Package buildbot :: Package changes :: Module p4poller
[frames] | no frames]

Source Code for Module buildbot.changes.p4poller

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16   
 17  # Many thanks to Dave Peticolas for contributing this module 
 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 
28 29 -class P4PollerError(Exception):
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
33 -def get_simple_split(branchfile):
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 # filled in when we're added 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):
68 # for backward compatibility; the parameter used to be spelled with 'i' 69 if pollinterval != -2: 70 pollInterval = pollinterval 71 72 self.p4port = p4port 73 self.p4user = p4user 74 self.p4passwd = p4passwd 75 self.p4base = p4base 76 self.p4bin = p4bin 77 self.split_file = split_file 78 self.pollInterval = pollInterval
79
80 - def describe(self):
81 return "p4source %s %s" % (self.p4port, self.p4base)
82
83 - def poll(self):
84 d = self._poll() 85 d.addErrback(log.err, 'P4 poll failed') 86 return d
87
88 - def _get_process_output(self, args):
89 env = dict([(e, os.environ.get(e)) for e in self.env_vars if os.environ.get(e)]) 90 d = utils.getProcessOutput(self.p4bin, args, env) 91 return d
92 93 @defer.deferredGenerator
94 - def _poll(self):
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 # first time through, the poller just gets a "baseline" for where to 123 # start on the next poll 124 log.msg('P4Poller: starting at change %d' % num) 125 self.last_change = num 126 return 127 changelists.append(num) 128 changelists.reverse() # oldest first 129 130 # Retrieve each sequentially. 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 # SF#1555985: Wade Brainerd reports a stray ^M at the end of the date 146 # field. The rstrip() is intended to remove that. 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) # affected files 157 158 branch_files = {} # dict for branch mapped to file(s) 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