Chromium Code Reviews| Index: trychange_git.py |
| diff --git a/trychange_git.py b/trychange_git.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..6a324dc36ddc73ff0fbd32459bfa3b5fdef54e15 |
| --- /dev/null |
| +++ b/trychange_git.py |
| @@ -0,0 +1,139 @@ |
| +#!/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.""" |
| + 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') |
|
cmp
2013/08/20 05:52:29
It would be for the best to enforce that TRYSERVER
agable
2013/08/20 07:48:08
Done.
|
| + 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__": |
|
cmp
2013/08/20 05:52:29
single quotes here instead of double quotes
agable
2013/08/20 07:48:08
Done.
|
| + sys.exit(Main(sys.argv)) |