blob: 28f708b168aabf9b9cbab96169c3322ff1afc3ef [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Client-side script to send local git changes to a tryserver.
It pushes the local feature branch to a private ref on googlesource
and posts a description of the job to an appengine instance, where it will get
picked up by the buildbot tryserver itself.
from __future__ import print_function
import json
import os
import subprocess
import sys
import urllib
def DieWithError(msg):
"""Prints the message to stderr and exits."""
print(msg, file=sys.stderr)
def RunGit(*args, **kwargs):
"""Runs the given git command with arguments, or dies.
Passes the given kwargs (e.g. cwd or env) through to subprocess."""
cmd = ('git',) + args
return subprocess.check_output(cmd, **kwargs).strip()
except subprocess.CalledProcessError as e:
DieWithError('Command "%s" failed.\n%s' % (' '.join(cmd), e))
def EnsureInGitRepo():
"""Quick sanity check to make sure we're in a git repo."""
if not RunGit('rev-parse', '--is-inside-work-tree') == 'true':
DieWithError('You don\'t appear to be inside a git repository.')
def GetCodeReviewSettings():
"""Reads codereview.settings and returns a dict of settings."""
top_dir = RunGit('rev-parse', '--show-toplevel')
this_dir = os.getcwd()
assert this_dir.startswith(top_dir), (top_dir, this_dir)
settings_file = os.path.join(this_dir, 'codereview.settings')
while not os.path.isfile(settings_file):
this_dir = os.path.split(this_dir)[0]
if not this_dir.startswith(top_dir):
DieWithError('Unable to find codereview.settings in this repo.')
settings_file = os.path.join(this_dir, 'codereview.settings')
settings = {}
with open(settings_file, 'r') as f:
for line in f.readlines():
if line.startswith('#'):
k, v = line.split(':', 1)
settings[k.strip()] = v.strip()
return settings
def PushBranch():
"""Pushes the current local branch to a ref in the try repo.
The try repo is either the remote called 'try', or 'origin' otherwise.
The ref is '/refs/try/<username>/<local branch>-<short hash>.
Returns the ref to which the local branch was pushed."""
username = RunGit('config', '--get', '').split('@', 1)[0]
branch = RunGit('symbolic-ref', '--short', '-q', 'HEAD')
commit = RunGit('rev-parse', branch)[:8]
remotes = RunGit('remote').splitlines()
if not all((username, branch, commit, remotes)):
DieWithError('Unable to get necessary git configuration.')
remote = 'try' if 'try' in remotes else 'origin'
ref = 'refs/try/%s/%s-%s' % (username, branch, commit)
RunGit('push', remote, '%s:%s' % (branch, ref))
return ref
def MakeJob(project, jobname, ref):
"""Creates a job description blob."""
email = RunGit('config', '--get', '')
repository = RunGit('config', '--get', 'remote.origin.url')
job = {
# Fields for buildbot sourcestamp.
'project': project,
'repository': repository,
'branch': ref,
'revision': 'HEAD',
# Fields for buildbot builder factory.
'buildername': jobname,
'recipe': project,
# Other useful fields.
'blamelist': [email],
return json.dumps(job)
def PostJob(server, project, job):
"""POSTs the job description blob to the tryserver instance."""
if not server.startswith('https://'):
DieWithError('Server URL must be https.')
url = '%s/%s/push' % (server, project)
data = urllib.urlencode({'job': job})
conn = urllib.urlopen(url, data)
except IOError as e:
response = conn.getcode()
if response != 200:
DieWithError('Failed to POST. Got: %d' % response)
def Main(_argv):
"""Main entry point."""
settings = GetCodeReviewSettings()
server = settings.get('TRYSERVER_HTTP_HOST')
project = settings.get('TRYSERVER_PROJECT')
jobnames = settings.get('TRYSERVER_JOB_NAME')
if not all((server, project, jobnames)):
DieWithError('Missing configuration in codereview.settings.')
ref = PushBranch()
for jobname in jobnames.split(','):
job = MakeJob(project, jobname, ref)
PostJob(server, project, job)
if __name__ == '__main__':