Chromium Code Reviews| 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 url = '%s/%s/push' % (server, project) | |
| 111 data = urllib.urlencode({'job': job}) | |
| 112 try: | |
| 113 conn = urllib.urlopen(url, data) | |
| 114 except IOError as e: | |
| 115 DieWithError(e) | |
| 116 response = conn.getcode() | |
| 117 if response != 200: | |
| 118 DieWithError('Failed to POST. Got: %d' % response) | |
| 119 | |
| 120 | |
| 121 def Main(argv): | |
| 122 """Main entry point.""" | |
| 123 EnsureInGitRepo() | |
| 124 | |
| 125 settings = GetCodeReviewSettings() | |
| 126 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.
| |
| 127 project = settings.get('TRYSERVER_PROJECT') | |
| 128 jobnames = settings.get('TRYSERVER_JOB_NAME') | |
| 129 if not all((server, project, jobnames)): | |
| 130 DieWithError('Missing configuration in codereview.settings.') | |
| 131 | |
| 132 ref = PushBranch() | |
| 133 for jobname in jobnames.split(','): | |
| 134 job = MakeJob(project, jobname, ref) | |
| 135 PostJob(server, project, job) | |
| 136 | |
| 137 | |
| 138 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.
| |
| 139 sys.exit(Main(sys.argv)) | |
| OLD | NEW |