1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
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
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):
88
90 return "p4source %s %s" % (self.p4port, self.p4base)
91
93 d = self._poll()
94 d.addErrback(log.err, 'P4 poll failed')
95 return d
96
101
102 @defer.inlineCallbacks
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
130
131 log.msg('P4Poller: starting at change %d' % num)
132 self.last_change = num
133 return
134 changelists.append(num)
135 changelists.reverse()
136
137
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
150 result = result.decode(self.encoding)
151
152 lines = result.split('\n')
153
154
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)
165
166 branch_files = {}
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