| Index: trychange_git.py
|
| diff --git a/trychange_git.py b/trychange_git.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..28f708b168aabf9b9cbab96169c3322ff1afc3ef
|
| --- /dev/null
|
| +++ b/trychange_git.py
|
| @@ -0,0 +1,141 @@
|
| +#!/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)
|
| + 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/try/<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/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', '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."""
|
| + EnsureInGitRepo()
|
| +
|
| + 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__':
|
| + sys.exit(Main(sys.argv))
|
|
|