1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import re
17 import xml.dom.minidom
18 import xml.parsers.expat
19
20 from twisted.python import log
21 from twisted.internet import defer
22
23 from buildbot.process import buildstep
24 from buildbot.steps.source import Source
25 from buildbot.interfaces import BuildSlaveTooOldError
26 from buildbot.config import ConfigErrors
27
28 -class SVN(Source):
29 """I perform Subversion checkout/update operations."""
30
31 name = 'svn'
32 branch_placeholder = '%%BRANCH%%'
33
34 renderables = [ 'repourl', 'baseURL' ]
35 possible_modes = ('incremental', 'full')
36 possible_methods = ('clean', 'fresh', 'clobber', 'copy', 'export', None)
37
38 - def __init__(self, repourl=None, baseURL=None, mode='incremental',
39 method=None, defaultBranch=None, username=None,
40 password=None, extra_args=None, keep_on_purge=None,
41 depth=None, **kwargs):
42
43 self.repourl = repourl
44 self.baseURL = baseURL
45 self.branch = defaultBranch
46 self.username = username
47 self.password = password
48 self.extra_args = extra_args
49 self.keep_on_purge = keep_on_purge or []
50 self.depth = depth
51 self.method=method
52 self.mode = mode
53 Source.__init__(self, **kwargs)
54 self.addFactoryArguments(repourl=repourl,
55 baseURL=baseURL,
56 mode=mode,
57 method=method,
58 defaultBranch=defaultBranch,
59 password=password,
60 username=username,
61 extra_args=extra_args,
62 keep_on_purge=keep_on_purge,
63 depth=depth,
64 )
65 errors = []
66 if self.mode not in self.possible_modes:
67 errors.append("mode %s is not one of %s" % (self.mode, self.possible_modes))
68 if self.method not in self.possible_methods:
69 errors.append("method %s is not one of %s" % (self.method, self.possible_methods))
70
71 if repourl and baseURL:
72 errors.append("you must provide exactly one of repourl and baseURL")
73
74 if repourl is None and baseURL is None:
75 errors.append("you must privide at least one of repourl and baseURL")
76 if errors:
77 raise ConfigErrors(errors)
78
79 - def startVC(self, branch, revision, patch):
90
91 if self.mode == 'full':
92 d.addCallback(self.full)
93 elif self.mode == 'incremental':
94 d.addCallback(self.incremental)
95 d.addCallback(self.parseGotRevision)
96 d.addCallback(self.finish)
97 d.addErrback(self.failed)
98 return d
99
100 @defer.deferredGenerator
102 if self.method == 'clobber':
103 wfd = defer.waitForDeferred(self.clobber())
104 yield wfd
105 wfd.getResult()
106 return
107 elif self.method in ['copy', 'export']:
108 wfd = defer.waitForDeferred(self.copy())
109 yield wfd
110 wfd.getResult()
111 return
112
113 wfd = defer.waitForDeferred(self._sourcedirIsUpdatable())
114 yield wfd
115 updatable = wfd.getResult()
116 if not updatable:
117
118 wfd = defer.waitForDeferred(
119 self._rmdir(self.workdir))
120 yield wfd
121 wfd.getResult()
122
123
124 checkout_cmd = ['checkout', self.repourl, '.']
125 if self.revision:
126 checkout_cmd.extend(["--revision", str(self.revision)])
127 wfd = defer.waitForDeferred(
128 self._dovccmd(checkout_cmd))
129 yield wfd
130 wfd.getResult()
131 elif self.method == 'clean':
132 wfd = defer.waitForDeferred(
133 self.clean())
134 yield wfd
135 wfd.getResult()
136 elif self.method == 'fresh':
137 wfd = defer.waitForDeferred(
138 self.fresh())
139 yield wfd
140 wfd.getResult()
141
142 @defer.deferredGenerator
144 wfd = defer.waitForDeferred(
145 self._sourcedirIsUpdatable())
146 yield wfd
147 updatable = wfd.getResult()
148
149 if not updatable:
150
151 wfd = defer.waitForDeferred(
152 self._rmdir(self.workdir))
153 yield wfd
154 wfd.getResult()
155
156
157 command = ['checkout', self.repourl, '.']
158 else:
159
160 command = ['update']
161 if self.revision:
162 command.extend(['--revision', str(self.revision)])
163
164 wfd = defer.waitForDeferred(
165 self._dovccmd(command))
166 yield wfd
167 wfd.getResult()
168
169 @defer.deferredGenerator
171 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir,
172 'logEnviron': self.logEnviron,})
173 cmd.useLog(self.stdio_log, False)
174 wfd = defer.waitForDeferred(
175 self.runCommand(cmd))
176 yield wfd
177 wfd.getResult()
178 if cmd.rc != 0:
179 raise buildstep.BuildStepFailed()
180
181 checkout_cmd = ['checkout', self.repourl, '.']
182 if self.revision:
183 checkout_cmd.extend(["--revision", str(self.revision)])
184
185 wfd = defer.waitForDeferred(
186 self._dovccmd(checkout_cmd))
187 yield wfd
188 wfd.getResult()
189
191 d = self.purge(True)
192 cmd = ['update']
193 if self.revision:
194 cmd.extend(['--revision', str(self.revision)])
195 d.addCallback(lambda _: self._dovccmd(cmd))
196 return d
197
199 d = self.purge(False)
200 cmd = ['update']
201 if self.revision:
202 cmd.extend(['--revision', str(self.revision)])
203 d.addCallback(lambda _: self._dovccmd(cmd))
204 return d
205
206 @defer.deferredGenerator
208 cmd = buildstep.RemoteCommand('rmdir', {'dir': self.workdir,
209 'logEnviron': self.logEnviron,})
210 cmd.useLog(self.stdio_log, False)
211 wfd = defer.waitForDeferred(
212 self.runCommand(cmd))
213 yield wfd
214 wfd.getResult()
215
216 if cmd.rc != 0:
217 raise buildstep.BuildStepFailed()
218
219
220 try:
221 old_workdir = self.workdir
222 self.workdir = 'source'
223 wfd = defer.waitForDeferred(
224 self.incremental(None))
225 yield wfd
226 wfd.getResult()
227 except:
228 self.workdir = old_workdir
229 raise
230 self.workdir = old_workdir
231
232
233 if self.method == 'copy':
234 cmd = buildstep.RemoteCommand('cpdir',
235 { 'fromdir': 'source', 'todir':self.workdir,
236 'logEnviron': self.logEnviron })
237 else:
238 export_cmd = ['svn', 'export']
239 if self.revision:
240 export_cmd.extend(["--revision", str(self.revision)])
241 export_cmd.extend(['source', self.workdir])
242
243 cmd = buildstep.RemoteShellCommand('', export_cmd,
244 env=self.env, logEnviron=self.logEnviron)
245 cmd.useLog(self.stdio_log, False)
246
247 wfd = defer.waitForDeferred(
248 self.runCommand(cmd))
249 yield wfd
250 wfd.getResult()
251
252 if cmd.rc != 0:
253 raise buildstep.BuildStepFailed()
254
260 d.addCallback(_gotResults)
261 d.addCallbacks(self.finished, self.checkDisconnect)
262 return d
263
264 @defer.deferredGenerator
275
276 - def _dovccmd(self, command, collectStdout=False):
277 assert command, "No command specified"
278 command.extend(['--non-interactive', '--no-auth-cache'])
279 if self.username:
280 command.extend(['--username', self.username])
281 if self.password:
282 command.extend(['--password', self.password])
283 if self.depth:
284 command.extend(['--depth', self.depth])
285 if self.extra_args:
286 command.extend(self.extra_args)
287
288 cmd = buildstep.RemoteShellCommand(self.workdir, ['svn'] + command,
289 env=self.env,
290 logEnviron=self.logEnviron,
291 collectStdout=collectStdout)
292 cmd.useLog(self.stdio_log, False)
293 log.msg("Starting SVN command : svn %s" % (" ".join(command), ))
294 d = self.runCommand(cmd)
295 def evaluateCommand(cmd):
296 if cmd.rc != 0:
297 log.msg("Source step failed while running command %s" % cmd)
298 raise buildstep.BuildStepFailed()
299 if collectStdout:
300 return cmd.stdout
301 else:
302 return cmd.rc
303 d.addCallback(lambda _: evaluateCommand(cmd))
304 return d
305
307 ''' Compute the svn url that will be passed to the svn remote command '''
308 if self.repourl:
309 return self.repourl
310 else:
311 if branch is None:
312 m = ("The SVN source step belonging to builder '%s' does not know "
313 "which branch to work with. This means that the change source "
314 "did not specify a branch and that defaultBranch is None." \
315 % self.build.builder.name)
316 raise RuntimeError(m)
317
318 computed = self.baseURL
319
320 if self.branch_placeholder in self.baseURL:
321 return computed.replace(self.branch_placeholder, branch)
322 else:
323 return computed + branch
324
326 if self.method is not None and self.mode != 'incremental':
327 return self.method
328 elif self.mode == 'incremental':
329 return None
330 elif self.method is None and self.mode == 'full':
331 return 'fresh'
332
333 @defer.deferredGenerator
335
336 cmd = buildstep.RemoteCommand('stat', {'file': self.workdir + '/.svn',
337 'logEnviron': self.logEnviron,})
338 cmd.useLog(self.stdio_log, False)
339 wfd = defer.waitForDeferred(
340 self.runCommand(cmd))
341 yield wfd
342 wfd.getResult()
343
344 if cmd.rc != 0:
345 yield False
346 return
347
348
349 wfd = defer.waitForDeferred(
350 self._dovccmd(['info'], collectStdout=True))
351 yield wfd
352 stdout = wfd.getResult()
353
354
355
356 mo = re.search('^URL:\s*(.*?)\s*$', stdout, re.M)
357 yield mo and mo.group(1) == self.repourl
358 return
359
388 d.addCallback(lambda _: _setrev(cmd.rc))
389 return d
390
391 - def purge(self, ignore_ignores):
415 d.addCallback(parseAndRemove)
416 def evaluateCommand(rc):
417 if rc != 0:
418 log.msg("Failed removing files")
419 raise buildstep.BuildStepFailed()
420 return rc
421 d.addCallback(evaluateCommand)
422 return d
423
424 @staticmethod
426 try:
427 result_xml = xml.dom.minidom.parseString(xmlStr)
428 except xml.parsers.expat.ExpatError:
429 log.err("Corrupted xml, aborting step")
430 raise buildstep.BuildStepFailed()
431
432 for entry in result_xml.getElementsByTagName('entry'):
433 (wc_status,) = entry.getElementsByTagName('wc-status')
434 if wc_status.getAttribute('item') == 'external':
435 continue
436 if wc_status.getAttribute('item') == 'missing':
437 continue
438 filename = entry.getAttribute('path')
439 if filename in keep_on_purge or filename == '':
440 continue
441 yield filename
442
443 @defer.deferredGenerator
456
467 d.addCallback(lambda _: evaluate(cmd))
468 return d
469
475