Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(383)

Side by Side Diff: scripts/master/tryjob_git_poller.py

Issue 8305010: Add ChromiumOS Trybot support. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # gitpoller.py taken from buildbot/build/third_party/buildbot_8_2/buildbot/chang es/gitpoller.py
2 #
3 # URL: http://buildbot.net
4 # Sources: http://github.com/buildbot/buildbot
5 # Version: 0.8.2
6 # License: GNU General Public License (GPL) Version 2
7 #
8 # Hack together to provide the feature in buildbot7
9 # Not placed in third_party since it's a mish-mash.
10 #
11 # All Chromium changes are labeled with 'Chromium change'. Current changes:
12 # 1) On git error, print the error to the log so you can debug.
13 # 2) Do NOT clear the environment when doing git commands; it throws
14 # away permissions (perhaps by deleting HOME so we can't find our SSH
15 # keys). 2 spots.
16 # 3) Add get_file_contents() function.
17
18
19 import time
20 import tempfile
21 import os
22 import subprocess
23
24 import select
25 import errno
26
27 from twisted.python import log, failure
28 from twisted.internet import reactor, utils
29 from twisted.internet.task import LoopingCall
30
31 from buildbot.changes import base, changes
32
33 class GitPoller(base.ChangeSource):
34 """This source will poll a remote git repo for changes and submit
35 them to the change master."""
36
37 compare_attrs = ["repourl", "branch", "workdir",
38 "pollinterval", "gitbin", "usetimestamps",
39 "category", "project"]
40
41 parent = None # filled in when we're added
42 loop = None
43 volatile = ['loop']
44 working = False
45 running = False
46
47 def __init__(self, repourl, branch='master',
48 workdir=None, pollinterval=10*60,
49 gitbin='git', usetimestamps=True,
50 category=None, project=''):
51 """
52 @type repourl: string
53 @param repourl: the url that describes the remote repository,
54 e.g. git:foobaz/myrepo.git
55
56 @type branch: string
57 @param branch: the desired branch to fetch, will default to 'master'
58
59 @type workdir: string
60 @param workdir: the directory where the poller should keep its local rep ository.
61 will default to <tempdir>/gitpoller_work
62
63 @type pollinterval: int
64 @param pollinterval: interval in seconds between polls, default is 10 mi nutes
65
66 @type gitbin: string
67 @param gitbin: path to the git binary, defaults to just 'git'
68
69 @type usetimestamps: boolean
70 @param usetimestamps: parse each revision's commit timestamp (default Tr ue), or
71 ignore it in favor of the current time (to appear together
72 in the waterfall page)
73
74 @type category: string
75 @param category: catergory associated with the change. Attached to
76 the Change object produced by this changesource suc h that
77 it can be targeted by change filters.
78
79 @type project string
80 @param project project that the changes are associated to. Attache d to
81 the Change object produced by this changesource suc h that
82 it can be targeted by change filters.
83 """
84
85 self.repourl = repourl
86 self.branch = branch
87 self.pollinterval = pollinterval
88 self.lastChange = time.time()
89 self.lastPoll = time.time()
90 self.gitbin = gitbin
91 self.workdir = workdir
92 self.usetimestamps = usetimestamps
93 self.category = category
94 self.project = project
95
96 if self.workdir == None:
97 self.workdir = tempfile.gettempdir() + '/gitpoller_work'
98
99 def startService(self):
100 self.loop = LoopingCall(self.poll)
101 base.ChangeSource.startService(self)
102
103 if not os.path.exists(self.workdir):
104 log.msg('gitpoller: creating working dir %s' % self.workdir)
105 os.makedirs(self.workdir)
106
107 if not os.path.exists(self.workdir + r'/.git'):
108 log.msg('gitpoller: initializing working dir')
109 os.system(self.gitbin + ' clone ' + self.repourl + ' ' + self.workdi r)
110
111 reactor.callLater(0, self.loop.start, self.pollinterval)
112
113 self.running = True
114
115 def stopService(self):
116 if self.running:
117 self.loop.stop()
118 self.running = False
119 return base.ChangeSource.stopService(self)
120
121 def describe(self):
122 status = ""
123 if not self.running:
124 status = "[STOPPED - check log]"
125 str = 'GitPoller watching the remote git repository %s, branch: %s %s' \
126 % (self.repourl, self.branch, status)
127 return str
128
129 def poll(self):
130 if self.working:
131 log.msg('gitpoller: not polling git repo because last poll is still working')
132 else:
133 self.working = True
134 d = self._get_changes()
135
136 d.addCallback(self._process_changes)
137 d.addCallbacks(self._changes_finished_ok, self._changes_finished_fai lure)
138 d.addCallback(self._catch_up)
139 d.addCallbacks(self._catch_up_finished_ok, self._catch_up__finished_ failure)
140 return
141
142 def get_file_contents(self, file_path):
143 log.msg('getting contents of file %s' % file_path)
144 # Need to catch up HEAD, since this can get called during the
145 # process_changes callback path.
146 p = subprocess.Popen(['git', 'reset', '--hard', 'FETCH_HEAD'],
147 cwd=self.workdir)
148 p.communicate()
149
150 p = subprocess.Popen(['cat', file_path], cwd=self.workdir,
151 stdout=subprocess.PIPE)
152 output, _ = p.communicate()
153
154 return output
155
156 def _get_git_output(self, args):
157 git_args = [self.gitbin] + args
158
159 p = subprocess.Popen(git_args,
160 cwd=self.workdir,
161 stdout=subprocess.PIPE)
162
163 # dirty hack - work around EINTR oddness on Mac builder
164 while True:
165 try:
166 output = p.communicate()[0]
167 break
168 except (OSError, select.error), e:
169 if e[0] == errno.EINTR:
170 continue
171 else:
172 raise
173
174 if p.returncode != 0:
175 raise EnvironmentError('call \'%s\' exited with error \'%s\', output : \'%s\'' %
176 (args, p.returncode, output))
177 return output
178
179 def _get_commit_comments(self, rev):
180 args = ['log', rev, '--no-walk', r'--format=%s%n%b']
181 output = self._get_git_output(args)
182
183 if len(output.strip()) == 0:
184 raise EnvironmentError('could not get commit comment for rev %s' % r ev)
185
186 return output
187
188 def _get_commit_timestamp(self, rev):
189 # unix timestamp
190 args = ['log', rev, '--no-walk', r'--format=%ct']
191 output = self._get_git_output(args)
192
193 try:
194 stamp = float(output)
195 except Exception, e:
196 log.msg('gitpoller: caught exception converting output \'%s\' to tim estamp' % output)
197 raise e
198
199 return stamp
200
201 def _get_commit_files(self, rev):
202 args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
203 fileList = self._get_git_output(args).split()
204 return fileList
205
206 def _get_commit_name(self, rev):
207 args = ['log', rev, '--no-walk', r'--format=%cn']
208 output = self._get_git_output(args)
209
210 if len(output.strip()) == 0:
211 raise EnvironmentError('could not get commit name for rev %s' % rev)
212
213 return output
214
215 def _get_changes(self):
216 log.msg('gitpoller: polling git repo at %s' % self.repourl)
217
218 self.lastPoll = time.time()
219
220 # get a deferred object that performs the fetch
221 args = ['fetch', self.repourl, self.branch]
222 # Chromium change: if we null out the environment we get git permission errors. E.g.
223 # 'git fetch' will 'Permission denied (publickey,gssapi-keyex,gssapi-wit h-mic,password).'
224 # For Clank we just use the environment.
225 env = os.environ
226 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=env , errortoo=1 )
227
228 return d
229
230 def _process_changes(self, res):
231 # get the change list
232
233 # Chromium change: make sure errors are super-clear
234 if 'Permission denied' in res:
235 print res
236
237 revListArgs = ['log', 'HEAD..FETCH_HEAD', r'--format=%H']
238 revs = self._get_git_output(revListArgs);
239 revCount = 0
240
241 # process oldest change first
242 revList = revs.split()
243 if revList:
244 revList.reverse()
245 revCount = len(revList)
246
247 log.msg('gitpoller: processing %d changes' % revCount )
248
249 for rev in revList:
250 if self.usetimestamps:
251 commit_timestamp = self._get_commit_timestamp(rev)
252 else:
253 commit_timestamp = None # use current time
254
255 c = changes.Change(who = self._get_commit_name(rev),
256 revision = rev,
257 files = self._get_commit_files(rev),
258 comments = self._get_commit_comments(rev),
259 when = commit_timestamp,
260 branch = self.branch,
261 category = self.category,
262 # CHANGED(bradnelson): project doesn't exist on
263 # our bb8 version.
264 #project = self.project,
265 repository = self.repourl)
266 self.parent.addChange(c)
267 self.lastChange = self.lastPoll
268
269 def _catch_up(self, res):
270 log.msg('gitpoller: catching up to FETCH_HEAD')
271
272 args = ['reset', '--hard', 'FETCH_HEAD']
273 # Chromium change: use the env
274 env = os.environ
275 d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=env)
276 return d;
277
278 def _changes_finished_ok(self, res):
279 assert self.working
280 # check for failure -- this is probably never hit but the twisted docs
281 # are not clear enough to be sure. it is being kept "just in case"
282 if isinstance(res, failure.Failure):
283 return self._changes_finished_failure(res)
284
285 return res
286
287 def _changes_finished_failure(self, res):
288 log.msg('gitpoller: repo poll failed: %s' % res)
289 assert self.working
290 # eat the failure to continue along the defered chain
291 # - we still want to catch up
292 return None
293
294 def _catch_up_finished_ok(self, res):
295 assert self.working
296
297 # check for failure -- this is probably never hit but the twisted docs
298 # are not clear enough to be sure. it is being kept "just in case"
299 if isinstance(res, failure.Failure):
300 return self._catch_up__finished_failure(res)
301
302 elif isinstance(res, tuple):
303 (stdout, stderr, code) = res
304 if code != 0:
305 e = EnvironmentError('catch up failed with exit code: %d' % code )
306 return self._catch_up__finished_failure(failure.Failure(e))
307
308 self.working = False
309 return res
310
311 def _catch_up__finished_failure(self, res):
312 assert self.working
313 assert isinstance(res, failure.Failure)
314 self.working = False
315
316 log.msg('gitpoller: catch up failed: %s' % res)
317 log.msg('gitpoller: stopping service - please resolve issues in local re po: %s' %
318 self.workdir)
319 self.stopService()
320 return res
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698