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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « scripts/master/try_job_base_bb7.py ('k') | site_config/config_default.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: scripts/master/tryjob_git_poller.py
===================================================================
--- scripts/master/tryjob_git_poller.py (revision 0)
+++ scripts/master/tryjob_git_poller.py (revision 0)
@@ -0,0 +1,320 @@
+# gitpoller.py taken from buildbot/build/third_party/buildbot_8_2/buildbot/changes/gitpoller.py
+#
+# URL: http://buildbot.net
+# Sources: http://github.com/buildbot/buildbot
+# Version: 0.8.2
+# License: GNU General Public License (GPL) Version 2
+#
+# Hack together to provide the feature in buildbot7
+# Not placed in third_party since it's a mish-mash.
+#
+# All Chromium changes are labeled with 'Chromium change'. Current changes:
+# 1) On git error, print the error to the log so you can debug.
+# 2) Do NOT clear the environment when doing git commands; it throws
+# away permissions (perhaps by deleting HOME so we can't find our SSH
+# keys). 2 spots.
+# 3) Add get_file_contents() function.
+
+
+import time
+import tempfile
+import os
+import subprocess
+
+import select
+import errno
+
+from twisted.python import log, failure
+from twisted.internet import reactor, utils
+from twisted.internet.task import LoopingCall
+
+from buildbot.changes import base, changes
+
+class GitPoller(base.ChangeSource):
+ """This source will poll a remote git repo for changes and submit
+ them to the change master."""
+
+ compare_attrs = ["repourl", "branch", "workdir",
+ "pollinterval", "gitbin", "usetimestamps",
+ "category", "project"]
+
+ parent = None # filled in when we're added
+ loop = None
+ volatile = ['loop']
+ working = False
+ running = False
+
+ def __init__(self, repourl, branch='master',
+ workdir=None, pollinterval=10*60,
+ gitbin='git', usetimestamps=True,
+ category=None, project=''):
+ """
+ @type repourl: string
+ @param repourl: the url that describes the remote repository,
+ e.g. git:foobaz/myrepo.git
+
+ @type branch: string
+ @param branch: the desired branch to fetch, will default to 'master'
+
+ @type workdir: string
+ @param workdir: the directory where the poller should keep its local repository.
+ will default to <tempdir>/gitpoller_work
+
+ @type pollinterval: int
+ @param pollinterval: interval in seconds between polls, default is 10 minutes
+
+ @type gitbin: string
+ @param gitbin: path to the git binary, defaults to just 'git'
+
+ @type usetimestamps: boolean
+ @param usetimestamps: parse each revision's commit timestamp (default True), or
+ ignore it in favor of the current time (to appear together
+ in the waterfall page)
+
+ @type category: string
+ @param category: catergory associated with the change. Attached to
+ the Change object produced by this changesource such that
+ it can be targeted by change filters.
+
+ @type project string
+ @param project project that the changes are associated to. Attached to
+ the Change object produced by this changesource such that
+ it can be targeted by change filters.
+ """
+
+ self.repourl = repourl
+ self.branch = branch
+ self.pollinterval = pollinterval
+ self.lastChange = time.time()
+ self.lastPoll = time.time()
+ self.gitbin = gitbin
+ self.workdir = workdir
+ self.usetimestamps = usetimestamps
+ self.category = category
+ self.project = project
+
+ if self.workdir == None:
+ self.workdir = tempfile.gettempdir() + '/gitpoller_work'
+
+ def startService(self):
+ self.loop = LoopingCall(self.poll)
+ base.ChangeSource.startService(self)
+
+ if not os.path.exists(self.workdir):
+ log.msg('gitpoller: creating working dir %s' % self.workdir)
+ os.makedirs(self.workdir)
+
+ if not os.path.exists(self.workdir + r'/.git'):
+ log.msg('gitpoller: initializing working dir')
+ os.system(self.gitbin + ' clone ' + self.repourl + ' ' + self.workdir)
+
+ reactor.callLater(0, self.loop.start, self.pollinterval)
+
+ self.running = True
+
+ def stopService(self):
+ if self.running:
+ self.loop.stop()
+ self.running = False
+ return base.ChangeSource.stopService(self)
+
+ def describe(self):
+ status = ""
+ if not self.running:
+ status = "[STOPPED - check log]"
+ str = 'GitPoller watching the remote git repository %s, branch: %s %s' \
+ % (self.repourl, self.branch, status)
+ return str
+
+ def poll(self):
+ if self.working:
+ log.msg('gitpoller: not polling git repo because last poll is still working')
+ else:
+ self.working = True
+ d = self._get_changes()
+
+ d.addCallback(self._process_changes)
+ d.addCallbacks(self._changes_finished_ok, self._changes_finished_failure)
+ d.addCallback(self._catch_up)
+ d.addCallbacks(self._catch_up_finished_ok, self._catch_up__finished_failure)
+ return
+
+ def get_file_contents(self, file_path):
+ log.msg('getting contents of file %s' % file_path)
+ # Need to catch up HEAD, since this can get called during the
+ # process_changes callback path.
+ p = subprocess.Popen(['git', 'reset', '--hard', 'FETCH_HEAD'],
+ cwd=self.workdir)
+ p.communicate()
+
+ p = subprocess.Popen(['cat', file_path], cwd=self.workdir,
+ stdout=subprocess.PIPE)
+ output, _ = p.communicate()
+
+ return output
+
+ def _get_git_output(self, args):
+ git_args = [self.gitbin] + args
+
+ p = subprocess.Popen(git_args,
+ cwd=self.workdir,
+ stdout=subprocess.PIPE)
+
+ # dirty hack - work around EINTR oddness on Mac builder
+ while True:
+ try:
+ output = p.communicate()[0]
+ break
+ except (OSError, select.error), e:
+ if e[0] == errno.EINTR:
+ continue
+ else:
+ raise
+
+ if p.returncode != 0:
+ raise EnvironmentError('call \'%s\' exited with error \'%s\', output: \'%s\'' %
+ (args, p.returncode, output))
+ return output
+
+ def _get_commit_comments(self, rev):
+ args = ['log', rev, '--no-walk', r'--format=%s%n%b']
+ output = self._get_git_output(args)
+
+ if len(output.strip()) == 0:
+ raise EnvironmentError('could not get commit comment for rev %s' % rev)
+
+ return output
+
+ def _get_commit_timestamp(self, rev):
+ # unix timestamp
+ args = ['log', rev, '--no-walk', r'--format=%ct']
+ output = self._get_git_output(args)
+
+ try:
+ stamp = float(output)
+ except Exception, e:
+ log.msg('gitpoller: caught exception converting output \'%s\' to timestamp' % output)
+ raise e
+
+ return stamp
+
+ def _get_commit_files(self, rev):
+ args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
+ fileList = self._get_git_output(args).split()
+ return fileList
+
+ def _get_commit_name(self, rev):
+ args = ['log', rev, '--no-walk', r'--format=%cn']
+ output = self._get_git_output(args)
+
+ if len(output.strip()) == 0:
+ raise EnvironmentError('could not get commit name for rev %s' % rev)
+
+ return output
+
+ def _get_changes(self):
+ log.msg('gitpoller: polling git repo at %s' % self.repourl)
+
+ self.lastPoll = time.time()
+
+ # get a deferred object that performs the fetch
+ args = ['fetch', self.repourl, self.branch]
+ # Chromium change: if we null out the environment we get git permission errors. E.g.
+ # 'git fetch' will 'Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).'
+ # For Clank we just use the environment.
+ env = os.environ
+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=env, errortoo=1 )
+
+ return d
+
+ def _process_changes(self, res):
+ # get the change list
+
+ # Chromium change: make sure errors are super-clear
+ if 'Permission denied' in res:
+ print res
+
+ revListArgs = ['log', 'HEAD..FETCH_HEAD', r'--format=%H']
+ revs = self._get_git_output(revListArgs);
+ revCount = 0
+
+ # process oldest change first
+ revList = revs.split()
+ if revList:
+ revList.reverse()
+ revCount = len(revList)
+
+ log.msg('gitpoller: processing %d changes' % revCount )
+
+ for rev in revList:
+ if self.usetimestamps:
+ commit_timestamp = self._get_commit_timestamp(rev)
+ else:
+ commit_timestamp = None # use current time
+
+ c = changes.Change(who = self._get_commit_name(rev),
+ revision = rev,
+ files = self._get_commit_files(rev),
+ comments = self._get_commit_comments(rev),
+ when = commit_timestamp,
+ branch = self.branch,
+ category = self.category,
+ # CHANGED(bradnelson): project doesn't exist on
+ # our bb8 version.
+ #project = self.project,
+ repository = self.repourl)
+ self.parent.addChange(c)
+ self.lastChange = self.lastPoll
+
+ def _catch_up(self, res):
+ log.msg('gitpoller: catching up to FETCH_HEAD')
+
+ args = ['reset', '--hard', 'FETCH_HEAD']
+ # Chromium change: use the env
+ env = os.environ
+ d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=env)
+ return d;
+
+ def _changes_finished_ok(self, res):
+ assert self.working
+ # check for failure -- this is probably never hit but the twisted docs
+ # are not clear enough to be sure. it is being kept "just in case"
+ if isinstance(res, failure.Failure):
+ return self._changes_finished_failure(res)
+
+ return res
+
+ def _changes_finished_failure(self, res):
+ log.msg('gitpoller: repo poll failed: %s' % res)
+ assert self.working
+ # eat the failure to continue along the defered chain
+ # - we still want to catch up
+ return None
+
+ def _catch_up_finished_ok(self, res):
+ assert self.working
+
+ # check for failure -- this is probably never hit but the twisted docs
+ # are not clear enough to be sure. it is being kept "just in case"
+ if isinstance(res, failure.Failure):
+ return self._catch_up__finished_failure(res)
+
+ elif isinstance(res, tuple):
+ (stdout, stderr, code) = res
+ if code != 0:
+ e = EnvironmentError('catch up failed with exit code: %d' % code)
+ return self._catch_up__finished_failure(failure.Failure(e))
+
+ self.working = False
+ return res
+
+ def _catch_up__finished_failure(self, res):
+ assert self.working
+ assert isinstance(res, failure.Failure)
+ self.working = False
+
+ log.msg('gitpoller: catch up failed: %s' % res)
+ log.msg('gitpoller: stopping service - please resolve issues in local repo: %s' %
+ self.workdir)
+ self.stopService()
+ return res
Property changes on: scripts/master/tryjob_git_poller.py
___________________________________________________________________
Added: svn:eol-style
+ LF
« no previous file with comments | « scripts/master/try_job_base_bb7.py ('k') | site_config/config_default.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698