1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 import time
17 import tempfile
18 import os
19 from twisted.python import log
20 from twisted.internet import defer, utils
21
22 from buildbot.util import deferredLocked
23 from buildbot.changes import base
24 from buildbot.util import epoch2datetime
27 """This source will poll a remote git repo for changes and submit
28 them to the change master."""
29
30 compare_attrs = ["repourl", "branch", "workdir",
31 "pollInterval", "gitbin", "usetimestamps",
32 "category", "project"]
33
34 - def __init__(self, repourl, branch='master',
35 workdir=None, pollInterval=10*60,
36 gitbin='git', usetimestamps=True,
37 category=None, project=None,
38 pollinterval=-2, fetch_refspec=None,
39 encoding='utf-8'):
65
83
84 @deferredLocked('initLock')
86 d = defer.succeed(None)
87 def make_dir(_):
88 dirpath = os.path.dirname(self.workdir.rstrip(os.sep))
89 if not os.path.exists(dirpath):
90 log.msg('gitpoller: creating parent directories for workdir')
91 os.makedirs(dirpath)
92 d.addCallback(make_dir)
93
94 def git_init(_):
95 log.msg('gitpoller: initializing working dir from %s' % self.repourl)
96 d = utils.getProcessOutputAndValue(self.gitbin,
97 ['init', self.workdir], env=os.environ)
98 d.addCallback(self._convert_nonzero_to_failure)
99 d.addErrback(self._stop_on_failure)
100 return d
101 d.addCallback(git_init)
102
103 def git_remote_add(_):
104 d = utils.getProcessOutputAndValue(self.gitbin,
105 ['remote', 'add', 'origin', self.repourl],
106 path=self.workdir, env=os.environ)
107 d.addCallback(self._convert_nonzero_to_failure)
108 d.addErrback(self._stop_on_failure)
109 return d
110 d.addCallback(git_remote_add)
111
112 def git_fetch_origin(_):
113 args = ['fetch', 'origin']
114 self._extend_with_fetch_refspec(args)
115 d = utils.getProcessOutputAndValue(self.gitbin, args,
116 path=self.workdir, env=os.environ)
117 d.addCallback(self._convert_nonzero_to_failure)
118 d.addErrback(self._stop_on_failure)
119 return d
120 d.addCallback(git_fetch_origin)
121
122 def set_master(_):
123 log.msg('gitpoller: checking out %s' % self.branch)
124 if self.branch == 'master':
125 d = utils.getProcessOutputAndValue(self.gitbin,
126 ['reset', '--hard', 'origin/%s' % self.branch],
127 path=self.workdir, env=os.environ)
128 else:
129 d = utils.getProcessOutputAndValue(self.gitbin,
130 ['checkout', '-b', self.branch, 'origin/%s' % self.branch],
131 path=self.workdir, env=os.environ)
132 d.addCallback(self._convert_nonzero_to_failure)
133 d.addErrback(self._stop_on_failure)
134 return d
135 d.addCallback(set_master)
136 def get_rev(_):
137 d = utils.getProcessOutputAndValue(self.gitbin,
138 ['rev-parse', self.branch],
139 path=self.workdir, env={})
140 d.addCallback(self._convert_nonzero_to_failure)
141 d.addErrback(self._stop_on_failure)
142 d.addCallback(lambda (out, err, code) : out.strip())
143 return d
144 d.addCallback(get_rev)
145 def print_rev(rev):
146 log.msg("gitpoller: finished initializing working dir from %s at rev %s"
147 % (self.repourl, rev))
148 d.addCallback(print_rev)
149 return d
150
152 status = ""
153 if not self.master:
154 status = "[STOPPED - check log]"
155 str = 'GitPoller watching the remote git repository %s, branch: %s %s' \
156 % (self.repourl, self.branch, status)
157 return str
158
159 @deferredLocked('initLock')
161 d = self._get_changes()
162 d.addCallback(self._process_changes)
163 d.addErrback(self._process_changes_failure)
164 d.addCallback(self._catch_up)
165 d.addErrback(self._catch_up_failure)
166 return d
167
176 d.addCallback(process)
177 return d
178
180
181 args = ['log', rev, '--no-walk', r'--format=%ct']
182 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
183 def process(git_output):
184 stripped_output = git_output.strip()
185 if self.usetimestamps:
186 try:
187 stamp = float(stripped_output)
188 except Exception, e:
189 log.msg('gitpoller: caught exception converting output \'%s\' to timestamp' % stripped_output)
190 raise e
191 return stamp
192 else:
193 return None
194 d.addCallback(process)
195 return d
196
198 args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
199 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
200 def process(git_output):
201 fileList = git_output.split()
202 return fileList
203 d.addCallback(process)
204 return d
205
207 args = ['log', rev, '--no-walk', r'--format=%aN <%aE>']
208 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
209 def process(git_output):
210 stripped_output = git_output.strip().decode(self.encoding)
211 if len(stripped_output) == 0:
212 raise EnvironmentError('could not get commit author for rev')
213 return stripped_output
214 d.addCallback(process)
215 return d
216
218 log.msg('gitpoller: polling git repo at %s' % self.repourl)
219
220 self.lastPoll = time.time()
221
222
223 args = ['fetch', 'origin']
224 self._extend_with_fetch_refspec(args)
225
226
227
228
229
230 d = utils.getProcessOutput(self.gitbin, args,
231 path=self.workdir,
232 env=os.environ, errortoo=True )
233
234 return d
235
236 @defer.deferredGenerator
238
239 revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'--format=%H']
240 self.changeCount = 0
241 d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir,
242 env=os.environ, errortoo=False )
243 wfd = defer.waitForDeferred(d)
244 yield wfd
245 results = wfd.getResult()
246
247
248 revList = results.split()
249 if not revList:
250 return
251
252 revList.reverse()
253 self.changeCount = len(revList)
254
255 log.msg('gitpoller: processing %d changes: %s in "%s"'
256 % (self.changeCount, revList, self.workdir) )
257
258 for rev in revList:
259 dl = defer.DeferredList([
260 self._get_commit_timestamp(rev),
261 self._get_commit_author(rev),
262 self._get_commit_files(rev),
263 self._get_commit_comments(rev),
264 ], consumeErrors=True)
265
266 wfd = defer.waitForDeferred(dl)
267 yield wfd
268 results = wfd.getResult()
269
270
271 failures = [ r[1] for r in results if not r[0] ]
272 if failures:
273
274 raise failures[0]
275
276 timestamp, author, files, comments = [ r[1] for r in results ]
277 d = self.master.addChange(
278 author=author,
279 revision=rev,
280 files=files,
281 comments=comments,
282 when_timestamp=epoch2datetime(timestamp),
283 branch=self.branch,
284 category=self.category,
285 project=self.project,
286 repository=self.repourl,
287 src='git')
288 wfd = defer.waitForDeferred(d)
289 yield wfd
290 results = wfd.getResult()
291
293 log.msg('gitpoller: repo poll failed')
294 log.err(f)
295
296 return None
297
299 if self.changeCount == 0:
300 log.msg('gitpoller: no changes, no catch_up')
301 return
302 log.msg('gitpoller: catching up tracking branch')
303 args = ['reset', '--hard', 'origin/%s' % (self.branch,)]
304 d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=os.environ)
305 d.addCallback(self._convert_nonzero_to_failure)
306 return d
307
309 log.err(f)
310 log.msg('gitpoller: please resolve issues in local repo: %s' % self.workdir)
311
312
313
315 "utility method to handle the result of getProcessOutputAndValue"
316 (stdout, stderr, code) = res
317 if code != 0:
318 raise EnvironmentError('command failed with exit code %d: %s' % (code, stderr))
319 return (stdout, stderr, code)
320
322 "utility method to stop the service when a failure occurs"
323 if self.running:
324 d = defer.maybeDeferred(lambda : self.stopService())
325 d.addErrback(log.err, 'while stopping broken GitPoller service')
326 return f
327
329 if self.fetch_refspec:
330 if type(self.fetch_refspec) in (list,set):
331 args.extend(self.fetch_refspec)
332 else:
333 args.append(self.fetch_refspec)
334