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  # Portions Copyright Buildbot Team Members 
 15  # Portions Copyright 2011 National Instruments 
 16   
 17   
 18  # Many thanks to Dave Peticolas for contributing this module 
 19   
 20  import re 
 21  import time 
 22  import os 
 23   
 24  from twisted.python import log 
 25  from twisted.internet import defer, utils 
 26   
 27  from buildbot import util 
 28  from buildbot.changes import base 
29 30 -class P4PollerError(Exception):
31 """Something went wrong with the poll. This is used as a distinctive 32 exception type so that unit tests can detect and ignore it."""
33
34 -def get_simple_split(branchfile):
35 """Splits the branchfile argument and assuming branch is 36 the first path component in branchfile, will return 37 branch and file else None.""" 38 39 index = branchfile.find('/') 40 if index == -1: return None, None 41 branch, file = branchfile.split('/', 1) 42 return branch, file
43
44 -class P4Source(base.PollingChangeSource, util.ComparableMixin):
45 """This source will poll a perforce repository for changes and submit 46 them to the change master.""" 47 48 compare_attrs = ["p4port", "p4user", "p4passwd", "p4base", 49 "p4bin", "pollInterval"] 50 51 env_vars = ["P4CLIENT", "P4PORT", "P4PASSWD", "P4USER", 52 "P4CHARSET"] 53 54 changes_line_re = re.compile( 55 r"Change (?P<num>\d+) on \S+ by \S+@\S+ '.*'$") 56 describe_header_re = re.compile( 57 r"Change \d+ by (?P<who>\S+)@\S+ on (?P<when>.+)$") 58 file_re = re.compile(r"^\.\.\. (?P<path>[^#]+)#\d+ [/\w]+$") 59 datefmt = '%Y/%m/%d %H:%M:%S' 60 61 parent = None # filled in when we're added 62 last_change = None 63 loop = None 64
65 - def __init__(self, p4port=None, p4user=None, p4passwd=None, 66 p4base='//', p4bin='p4', 67 split_file=lambda branchfile: (None, branchfile), 68 pollInterval=60 * 10, histmax=None, pollinterval=-2, 69 encoding='utf8', project=None, name=None):
70 71 # for backward compatibility; the parameter used to be spelled with 'i' 72 if pollinterval != -2: 73 pollInterval = pollinterval 74 75 base.PollingChangeSource.__init__(self, name=name, pollInterval=pollInterval) 76 77 if project is None: 78 project = '' 79 80 self.p4port = p4port 81 self.p4user = p4user 82 self.p4passwd = p4passwd 83 self.p4base = p4base 84 self.p4bin = p4bin 85 self.split_file = split_file 86 self.encoding = encoding 87 self.project = project
88
89 - def describe(self):
90 return "p4source %s %s" % (self.p4port, self.p4base)
91
92 - def poll(self):
93 d = self._poll() 94 d.addErrback(log.err, 'P4 poll failed') 95 return d
96
97 - def _get_process_output(self, args):
98 env = dict([(e, os.environ.get(e)) for e in self.env_vars if os.environ.get(e)]) 99 d = utils.getProcessOutput(self.p4bin, args, env) 100 return d
101 102 @defer.inlineCallbacks
103 - def _poll(self):
104 args = [] 105 if self.p4port: 106 args.extend(['-p', self.p4port]) 107 if self.p4user: 108 args.extend(['-u', self.p4user]) 109 if self.p4passwd: 110 args.extend(['-P', self.p4passwd]) 111 args.extend(['changes']) 112 if self.last_change is not None: 113 args.extend(['%s...@%d,now' % (self.p4base, self.last_change+1)]) 114 else: 115 args.extend(['-m', '1', '%s...' % (self.p4base,)]) 116 117 result = yield self._get_process_output(args) 118 119 last_change = self.last_change 120 changelists = [] 121 for line in result.split('\n'): 122 line = line.strip() 123 if not line: continue 124 m = self.changes_line_re.match(line) 125 if not m: 126 raise P4PollerError("Unexpected 'p4 changes' output: %r" % result) 127 num = int(m.group('num')) 128 if last_change is None: 129 # first time through, the poller just gets a "baseline" for where to 130 # start on the next poll 131 log.msg('P4Poller: starting at change %d' % num) 132 self.last_change = num 133 return 134 changelists.append(num) 135 changelists.reverse() # oldest first 136 137 # Retrieve each sequentially. 138 for num in changelists: 139 args = [] 140 if self.p4port: 141 args.extend(['-p', self.p4port]) 142 if self.p4user: 143 args.extend(['-u', self.p4user]) 144 if self.p4passwd: 145 args.extend(['-P', self.p4passwd]) 146 args.extend(['describe', '-s', str(num)]) 147 result = yield self._get_process_output(args) 148 149 # decode the result from its designated encoding 150 result = result.decode(self.encoding) 151 152 lines = result.split('\n') 153 # SF#1555985: Wade Brainerd reports a stray ^M at the end of the date 154 # field. The rstrip() is intended to remove that. 155 lines[0] = lines[0].rstrip() 156 m = self.describe_header_re.match(lines[0]) 157 if not m: 158 raise P4PollerError("Unexpected 'p4 describe -s' result: %r" % result) 159 who = m.group('who') 160 when = time.mktime(time.strptime(m.group('when'), self.datefmt)) 161 comments = '' 162 while not lines[0].startswith('Affected files'): 163 comments += lines.pop(0) + '\n' 164 lines.pop(0) # affected files 165 166 branch_files = {} # dict for branch mapped to file(s) 167 while lines: 168 line = lines.pop(0).strip() 169 if not line: continue 170 m = self.file_re.match(line) 171 if not m: 172 raise P4PollerError("Invalid file line: %r" % line) 173 path = m.group('path') 174 if path.startswith(self.p4base): 175 branch, file = self.split_file(path[len(self.p4base):]) 176 if (branch == None and file == None): continue 177 if branch_files.has_key(branch): 178 branch_files[branch].append(file) 179 else: 180 branch_files[branch] = [file] 181 182 for branch in branch_files: 183 yield self.master.addChange( 184 author=who, 185 files=branch_files[branch], 186 comments=comments, 187 revision=str(num), 188 when_timestamp=util.epoch2datetime(when), 189 branch=branch, 190 project=self.project) 191 192 self.last_change = num
193