1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 from twisted.python import log
22 from twisted.internet import defer, utils
23
24 from buildbot import util
25 from buildbot.changes import base
26
27 import xml.dom.minidom
28 import os, urllib
34
36
37
38 pieces = path.split('/')
39 if pieces[0] == 'trunk':
40 return (None, '/'.join(pieces[1:]))
41 elif pieces[0] == 'branches':
42 return ('/'.join(pieces[0:2]), '/'.join(pieces[2:]))
43 else:
44 return None
45
46
47 -class SVNPoller(base.PollingChangeSource, util.ComparableMixin):
48 """
49 Poll a Subversion repository for changes and submit them to the change
50 master.
51 """
52
53 compare_attrs = ["svnurl", "split_file",
54 "svnuser", "svnpasswd",
55 "pollInterval", "histmax",
56 "svnbin", "category", "cachepath"]
57
58 parent = None
59 last_change = None
60 loop = None
61
62 - def __init__(self, svnurl, split_file=None,
63 svnuser=None, svnpasswd=None,
64 pollInterval=10*60, histmax=100,
65 svnbin='svn', revlinktmpl='', category=None,
66 project='', cachepath=None, pollinterval=-2):
67
68 if pollinterval != -2:
69 pollInterval = pollinterval
70
71 if svnurl.endswith("/"):
72 svnurl = svnurl[:-1]
73 self.svnurl = svnurl
74 self.split_file = split_file or split_file_alwaystrunk
75 self.svnuser = svnuser
76 self.svnpasswd = svnpasswd
77
78 self.revlinktmpl = revlinktmpl
79
80 self.environ = os.environ.copy()
81
82
83 self.svnbin = svnbin
84 self.pollInterval = pollInterval
85 self.histmax = histmax
86 self._prefix = None
87 self.category = category
88 self.project = project
89
90 self.cachepath = cachepath
91 if self.cachepath and os.path.exists(self.cachepath):
92 try:
93 f = open(self.cachepath, "r")
94 self.last_change = int(f.read().strip())
95 log.msg("SVNPoller: SVNPoller(%s) setting last_change to %s" % (self.svnurl, self.last_change))
96 f.close()
97
98 f = open(self.cachepath, "w")
99 f.write(str(self.last_change))
100 f.close()
101 except:
102 self.cachepath = None
103 log.msg(("SVNPoller: SVNPoller(%s) cache file corrupt or unwriteable; " +
104 "skipping and not using") % self.svnurl)
105 log.err()
106
108 return "SVNPoller: watching %s" % self.svnurl
109
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 if self.project:
143 log.msg("SVNPoller: polling " + self.project)
144 else:
145 log.msg("SVNPoller: polling")
146
147 d = defer.succeed(None)
148 if not self._prefix:
149 d.addCallback(lambda _ : self.get_prefix())
150 def set_prefix(prefix):
151 self._prefix = prefix
152 d.addCallback(set_prefix)
153
154 d.addCallback(self.get_logs)
155 d.addCallback(self.parse_logs)
156 d.addCallback(self.get_new_logentries)
157 d.addCallback(self.create_changes)
158 d.addCallback(self.submit_changes)
159 d.addCallback(self.finished_ok)
160 d.addErrback(log.err, 'SVNPoller: Error in while polling')
161 return d
162
167
169 args = ["info", "--xml", "--non-interactive", self.svnurl]
170 if self.svnuser:
171 args.extend(["--username=%s" % self.svnuser])
172 if self.svnpasswd:
173 args.extend(["--password=%s" % self.svnpasswd])
174 d = self.getProcessOutput(args)
175 def determine_prefix(output):
176 try:
177 doc = xml.dom.minidom.parseString(output)
178 except xml.parsers.expat.ExpatError:
179 log.msg("SVNPoller: SVNPoller._determine_prefix_2: ExpatError in '%s'"
180 % output)
181 raise
182 rootnodes = doc.getElementsByTagName("root")
183 if not rootnodes:
184
185
186 self._prefix = ""
187 return self._prefix
188 rootnode = rootnodes[0]
189 root = "".join([c.data for c in rootnode.childNodes])
190
191 assert self.svnurl.startswith(root), \
192 ("svnurl='%s' doesn't start with <root>='%s'" %
193 (self.svnurl, root))
194 prefix = self.svnurl[len(root):]
195 if prefix.startswith("/"):
196 prefix = prefix[1:]
197 log.msg("SVNPoller: svnurl=%s, root=%s, so prefix=%s" %
198 (self.svnurl, root, prefix))
199 return prefix
200 d.addCallback(determine_prefix)
201 return d
202
204 args = []
205 args.extend(["log", "--xml", "--verbose", "--non-interactive"])
206 if self.svnuser:
207 args.extend(["--username=%s" % self.svnuser])
208 if self.svnpasswd:
209 args.extend(["--password=%s" % self.svnpasswd])
210 args.extend(["--limit=%d" % (self.histmax), self.svnurl])
211 d = self.getProcessOutput(args)
212 return d
213
215
216 try:
217 doc = xml.dom.minidom.parseString(output)
218 except xml.parsers.expat.ExpatError:
219 log.msg("SVNPoller: SVNPoller.parse_logs: ExpatError in '%s'" % output)
220 raise
221 logentries = doc.getElementsByTagName("logentry")
222 return logentries
223
224
226 last_change = old_last_change = self.last_change
227
228
229
230
231
232 new_last_change = None
233 new_logentries = []
234 if logentries:
235 new_last_change = int(logentries[0].getAttribute("revision"))
236
237 if last_change is None:
238
239
240
241 log.msg('SVNPoller: starting at change %s' % new_last_change)
242 elif last_change == new_last_change:
243
244 log.msg('SVNPoller: no changes')
245 else:
246 for el in logentries:
247 if last_change == int(el.getAttribute("revision")):
248 break
249 new_logentries.append(el)
250 new_logentries.reverse()
251
252 self.last_change = new_last_change
253 log.msg('SVNPoller: _process_changes %s .. %s' %
254 (old_last_change, new_last_change))
255 return new_logentries
256
257
258 - def _get_text(self, element, tag_name):
259 try:
260 child_nodes = element.getElementsByTagName(tag_name)[0].childNodes
261 text = "".join([t.data for t in child_nodes])
262 except:
263 text = "<unknown>"
264 return text
265
276
278 changes = []
279
280 for el in new_logentries:
281 revision = str(el.getAttribute("revision"))
282
283 revlink=''
284
285 if self.revlinktmpl:
286 if revision:
287 revlink = self.revlinktmpl % urllib.quote_plus(revision)
288
289 log.msg("Adding change revision %s" % (revision,))
290 author = self._get_text(el, "author")
291 comments = self._get_text(el, "msg")
292
293
294
295
296
297 branches = {}
298 try:
299 pathlist = el.getElementsByTagName("paths")[0]
300 except IndexError:
301 log.msg("ignoring commit with no paths")
302 continue
303
304 for p in pathlist.getElementsByTagName("path"):
305 action = p.getAttribute("action")
306 path = "".join([t.data for t in p.childNodes])
307
308
309
310
311 path = path.encode("ascii")
312 if path.startswith("/"):
313 path = path[1:]
314 where = self._transform_path(path)
315
316
317
318 if where:
319 branch, filename = where
320 if not branch in branches:
321 branches[branch] = { 'files': []}
322 branches[branch]['files'].append(filename)
323
324 if not branches[branch].has_key('action'):
325 branches[branch]['action'] = action
326
327 for branch in branches.keys():
328 action = branches[branch]['action']
329 files = branches[branch]['files']
330 number_of_files_changed = len(files)
331
332 if action == u'D' and number_of_files_changed == 1 and files[0] == '':
333 log.msg("Ignoring deletion of branch '%s'" % branch)
334 else:
335 chdict = dict(
336 author=author,
337 files=files,
338 comments=comments,
339 revision=revision,
340 branch=branch,
341 revlink=revlink,
342 category=self.category,
343 repository=self.svnurl,
344 project = self.project)
345 changes.append(chdict)
346
347 return changes
348
349 @defer.deferredGenerator
351 for chdict in changes:
352 wfd = defer.waitForDeferred(self.master.addChange(src='svn',
353 **chdict))
354 yield wfd
355 wfd.getResult()
356
358 if self.cachepath:
359 f = open(self.cachepath, "w")
360 f.write(str(self.last_change))
361 f.close()
362
363 log.msg("SVNPoller: finished polling %s" % res)
364 return res
365