1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19  import re 
 20  import time 
 21  import os 
 22   
 23  from twisted.python import log 
 24  from twisted.internet import defer, utils 
 25   
 26  from buildbot import util 
 27  from buildbot.changes import base 
 30      """Something went wrong with the poll. This is used as a distinctive 
 31      exception type so that unit tests can detect and ignore it.""" 
  32   
 34      """Splits the branchfile argument and assuming branch is  
 35         the first path component in branchfile, will return 
 36         branch and file else None.""" 
 37   
 38      index = branchfile.find('/') 
 39      if index == -1: return None, None 
 40      branch, file = branchfile.split('/', 1) 
 41      return branch, file 
  42   
 43 -class P4Source(base.PollingChangeSource, util.ComparableMixin): 
  44      """This source will poll a perforce repository for changes and submit 
 45      them to the change master.""" 
 46   
 47      compare_attrs = ["p4port", "p4user", "p4passwd", "p4base", 
 48                       "p4bin", "pollInterval"] 
 49   
 50      env_vars = ["P4CLIENT", "P4PORT", "P4PASSWD", "P4USER", 
 51                  "P4CHARSET"] 
 52   
 53      changes_line_re = re.compile( 
 54              r"Change (?P<num>\d+) on \S+ by \S+@\S+ '.*'$") 
 55      describe_header_re = re.compile( 
 56              r"Change \d+ by (?P<who>\S+)@\S+ on (?P<when>.+)$") 
 57      file_re = re.compile(r"^\.\.\. (?P<path>[^#]+)#\d+ [/\w]+$") 
 58      datefmt = '%Y/%m/%d %H:%M:%S' 
 59   
 60      parent = None  
 61      last_change = None 
 62      loop = None 
 63   
 64 -    def __init__(self, p4port=None, p4user=None, p4passwd=None, 
 65                   p4base='//', p4bin='p4', 
 66                   split_file=lambda branchfile: (None, branchfile), 
 67                   pollInterval=60 * 10, histmax=None, pollinterval=-2): 
  79   
 81          return "p4source %s %s" % (self.p4port, self.p4base) 
  82   
 84          d = self._poll() 
 85          d.addErrback(log.err, 'P4 poll failed') 
 86          return d 
  87   
 92   
 93      @defer.deferredGenerator 
 95          args = [] 
 96          if self.p4port: 
 97              args.extend(['-p', self.p4port]) 
 98          if self.p4user: 
 99              args.extend(['-u', self.p4user]) 
100          if self.p4passwd: 
101              args.extend(['-P', self.p4passwd]) 
102          args.extend(['changes']) 
103          if self.last_change is not None: 
104              args.extend(['%s...@%d,now' % (self.p4base, self.last_change+1)]) 
105          else: 
106              args.extend(['-m', '1', '%s...' % (self.p4base,)]) 
107   
108          wfd = defer.waitForDeferred(self._get_process_output(args)) 
109          yield wfd 
110          result = wfd.getResult() 
111   
112          last_change = self.last_change 
113          changelists = [] 
114          for line in result.split('\n'): 
115              line = line.strip() 
116              if not line: continue 
117              m = self.changes_line_re.match(line) 
118              if not m: 
119                  raise P4PollerError("Unexpected 'p4 changes' output: %r" % result) 
120              num = int(m.group('num')) 
121              if last_change is None: 
122                   
123                   
124                  log.msg('P4Poller: starting at change %d' % num) 
125                  self.last_change = num 
126                  return 
127              changelists.append(num) 
128          changelists.reverse()  
129   
130           
131          for num in changelists: 
132              args = [] 
133              if self.p4port: 
134                  args.extend(['-p', self.p4port]) 
135              if self.p4user: 
136                  args.extend(['-u', self.p4user]) 
137              if self.p4passwd: 
138                  args.extend(['-P', self.p4passwd]) 
139              args.extend(['describe', '-s', str(num)]) 
140              wfd = defer.waitForDeferred(self._get_process_output(args)) 
141              yield wfd 
142              result = wfd.getResult() 
143   
144              lines = result.split('\n') 
145               
146               
147              lines[0] = lines[0].rstrip() 
148              m = self.describe_header_re.match(lines[0]) 
149              if not m: 
150                  raise P4PollerError("Unexpected 'p4 describe -s' result: %r" % result) 
151              who = m.group('who') 
152              when = time.mktime(time.strptime(m.group('when'), self.datefmt)) 
153              comments = '' 
154              while not lines[0].startswith('Affected files'): 
155                  comments += lines.pop(0) + '\n' 
156              lines.pop(0)  
157   
158              branch_files = {}  
159              while lines: 
160                  line = lines.pop(0).strip() 
161                  if not line: continue 
162                  m = self.file_re.match(line) 
163                  if not m: 
164                      raise P4PollerError("Invalid file line: %r" % line) 
165                  path = m.group('path') 
166                  if path.startswith(self.p4base): 
167                      branch, file = self.split_file(path[len(self.p4base):]) 
168                      if (branch == None and file == None): continue 
169                      if branch_files.has_key(branch): 
170                          branch_files[branch].append(file) 
171                      else: 
172                          branch_files[branch] = [file] 
173   
174              for branch in branch_files: 
175                  d = self.master.addChange( 
176                         author=who, 
177                         files=branch_files[branch], 
178                         comments=comments, 
179                         revision=str(num), 
180                         when_timestamp=util.epoch2datetime(when), 
181                         branch=branch) 
182                  wfd = defer.waitForDeferred(d) 
183                  yield wfd 
184                  wfd.getResult() 
185   
186              self.last_change = num 
  187