OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """Client-side script to send local git changes to a tryserver. |
| 7 |
| 8 It pushes the local feature branch to a private ref on googlesource |
| 9 and posts a description of the job to an appengine instance, where it will get |
| 10 picked up by the buildbot tryserver itself. |
| 11 """ |
| 12 |
| 13 from __future__ import print_function |
| 14 |
| 15 import json |
| 16 import os |
| 17 import subprocess |
| 18 import sys |
| 19 import urllib |
| 20 |
| 21 |
| 22 def DieWithError(msg): |
| 23 """Prints the message to stderr and exits.""" |
| 24 print(msg, file=sys.stderr) |
| 25 sys.exit(1) |
| 26 |
| 27 |
| 28 def RunGit(*args, **kwargs): |
| 29 """Runs the given git command with arguments, or dies. |
| 30 |
| 31 Passes the given kwargs (e.g. cwd or env) through to subprocess.""" |
| 32 cmd = ('git',) + args |
| 33 try: |
| 34 return subprocess.check_output(cmd, **kwargs).strip() |
| 35 except subprocess.CalledProcessError as e: |
| 36 DieWithError('Command "%s" failed.\n%s' % (' '.join(cmd), e)) |
| 37 |
| 38 |
| 39 def EnsureInGitRepo(): |
| 40 """Quick sanity check to make sure we're in a git repo.""" |
| 41 if not RunGit('rev-parse', '--is-inside-work-tree') == 'true': |
| 42 DieWithError('You don\'t appear to be inside a git repository.') |
| 43 |
| 44 |
| 45 def GetCodeReviewSettings(): |
| 46 """Reads codereview.settings and returns a dict of settings.""" |
| 47 top_dir = RunGit('rev-parse', '--show-toplevel') |
| 48 this_dir = os.getcwd() |
| 49 assert this_dir.startswith(top_dir), (top_dir, this_dir) |
| 50 |
| 51 settings_file = os.path.join(this_dir, 'codereview.settings') |
| 52 while not os.path.isfile(settings_file): |
| 53 this_dir = os.path.split(this_dir)[0] |
| 54 if not this_dir.startswith(top_dir): |
| 55 DieWithError('Unable to find codereview.settings in this repo.') |
| 56 settings_file = os.path.join(this_dir, 'codereview.settings') |
| 57 |
| 58 settings = {} |
| 59 with open(settings_file, 'r') as f: |
| 60 for line in f.readlines(): |
| 61 if line.startswith('#'): |
| 62 continue |
| 63 k, v = line.split(':', 1) |
| 64 settings[k.strip()] = v.strip() |
| 65 return settings |
| 66 |
| 67 |
| 68 def PushBranch(): |
| 69 """Pushes the current local branch to a ref in the try repo. |
| 70 |
| 71 The try repo is either the remote called 'try', or 'origin' otherwise. |
| 72 The ref is '/refs/try/<username>/<local branch>-<short hash>. |
| 73 |
| 74 Returns the ref to which the local branch was pushed.""" |
| 75 username = RunGit('config', '--get', 'user.email').split('@', 1)[0] |
| 76 branch = RunGit('symbolic-ref', '--short', '-q', 'HEAD') |
| 77 commit = RunGit('rev-parse', branch)[:8] |
| 78 remotes = RunGit('remote').splitlines() |
| 79 if not all((username, branch, commit, remotes)): |
| 80 DieWithError('Unable to get necessary git configuration.') |
| 81 |
| 82 remote = 'try' if 'try' in remotes else 'origin' |
| 83 ref = 'refs/try/%s/%s-%s' % (username, branch, commit) |
| 84 |
| 85 RunGit('push', remote, '%s:%s' % (branch, ref)) |
| 86 return ref |
| 87 |
| 88 |
| 89 def MakeJob(project, jobname, ref): |
| 90 """Creates a job description blob.""" |
| 91 email = RunGit('config', '--get', 'user.email') |
| 92 repository = RunGit('config', '--get', 'remote.origin.url') |
| 93 job = { |
| 94 # Fields for buildbot sourcestamp. |
| 95 'project': project, |
| 96 'repository': repository, |
| 97 'branch': ref, |
| 98 'revision': 'HEAD', |
| 99 # Fields for buildbot builder factory. |
| 100 'buildername': jobname, |
| 101 'recipe': project, |
| 102 # Other useful fields. |
| 103 'blamelist': [email], |
| 104 } |
| 105 return json.dumps(job) |
| 106 |
| 107 |
| 108 def PostJob(server, project, job): |
| 109 """POSTs the job description blob to the tryserver instance.""" |
| 110 if not server.startswith('https://'): |
| 111 DieWithError('Server URL must be https.') |
| 112 url = '%s/%s/push' % (server, project) |
| 113 data = urllib.urlencode({'job': job}) |
| 114 try: |
| 115 conn = urllib.urlopen(url, data) |
| 116 except IOError as e: |
| 117 DieWithError(e) |
| 118 response = conn.getcode() |
| 119 if response != 200: |
| 120 DieWithError('Failed to POST. Got: %d' % response) |
| 121 |
| 122 |
| 123 def Main(_argv): |
| 124 """Main entry point.""" |
| 125 EnsureInGitRepo() |
| 126 |
| 127 settings = GetCodeReviewSettings() |
| 128 server = settings.get('TRYSERVER_HTTP_HOST') |
| 129 project = settings.get('TRYSERVER_PROJECT') |
| 130 jobnames = settings.get('TRYSERVER_JOB_NAME') |
| 131 if not all((server, project, jobnames)): |
| 132 DieWithError('Missing configuration in codereview.settings.') |
| 133 |
| 134 ref = PushBranch() |
| 135 for jobname in jobnames.split(','): |
| 136 job = MakeJob(project, jobname, ref) |
| 137 PostJob(server, project, job) |
| 138 |
| 139 |
| 140 if __name__ == '__main__': |
| 141 sys.exit(Main(sys.argv)) |
OLD | NEW |