| #!/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 try-ref on the central repo |
| 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) |
| sys.exit(1) |
| |
| |
| 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 |
| try: |
| 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('#'): |
| continue |
| 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/tryjobs/<username>/<local branch>-<short hash>. |
| |
| Returns the ref to which the local branch was pushed.""" |
| username = RunGit('config', '--get', 'user.email').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/tryjobs/%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', 'user.email') |
| 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}) |
| try: |
| conn = urllib.urlopen(url, data) |
| except IOError as e: |
| DieWithError(e) |
| response = conn.getcode() |
| if response != 200: |
| DieWithError('Failed to POST. Got: %d' % response) |
| |
| |
| def Main(_argv): |
| """Main entry point.""" |
| # Sanity check. |
| EnsureInGitRepo() |
| |
| # Get some settings. |
| 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.') |
| |
| # Do the heavy lifting. |
| ref = PushBranch() |
| for jobname in jobnames.split(','): |
| job = MakeJob(project, jobname, ref) |
| PostJob(server, project, job) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(Main(sys.argv)) |