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                              who=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(**chdict)) 
353              yield wfd 
354              wfd.getResult() 
 355   
357          if self.cachepath: 
358              f = open(self.cachepath, "w") 
359              f.write(str(self.last_change)) 
360              f.close() 
361   
362          log.msg("SVNPoller: finished polling %s" % res) 
363          return res 
 364