Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Side by Side Diff: git_cl.py

Issue 1063103002: git cl try uses buildbucket via OAuth2 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 # Copyright (C) 2008 Evan Martin <martine@danga.com> 6 # Copyright (C) 2008 Evan Martin <martine@danga.com>
7 7
8 """A git-command for integrating reviews on Rietveld.""" 8 """A git-command for integrating reviews on Rietveld."""
9 9
10 from distutils.version import LooseVersion 10 from distutils.version import LooseVersion
11 from multiprocessing.pool import ThreadPool 11 from multiprocessing.pool import ThreadPool
12 import base64 12 import base64
13 import glob 13 import glob
14 import httplib
14 import json 15 import json
15 import logging 16 import logging
16 import optparse 17 import optparse
17 import os 18 import os
18 import Queue 19 import Queue
19 import re 20 import re
20 import stat 21 import stat
21 import sys 22 import sys
22 import tempfile 23 import tempfile
23 import textwrap 24 import textwrap
25 import threading
26 import time
24 import urllib2 27 import urllib2
25 import urlparse 28 import urlparse
26 import webbrowser 29 import webbrowser
27 import zlib 30 import zlib
28 31
29 try: 32 try:
30 import readline # pylint: disable=F0401,W0611 33 import readline # pylint: disable=F0401,W0611
31 except ImportError: 34 except ImportError:
32 pass 35 pass
33 36
34 37
35 from third_party import colorama 38 from third_party import colorama
39 from third_party import httplib2
36 from third_party import upload 40 from third_party import upload
41 from third_party.google_api_python_client import apiclient
Vadim Sh. 2015/04/10 02:24:29 it is not vendored properly: Traceback (most rece
nodir 2015/04/13 16:04:53 "import apiclient" should work, we use it in other
sheyang 2015/04/14 01:30:22 Per discussion with Nodir@ I added the directory t
37 import breakpad # pylint: disable=W0611 42 import breakpad # pylint: disable=W0611
38 import clang_format 43 import clang_format
39 import dart_format 44 import dart_format
40 import fix_encoding 45 import fix_encoding
41 import gclient_utils 46 import gclient_utils
42 import git_common 47 import git_common
48 import oauth2
Vadim Sh. 2015/04/10 02:24:29 this file has been replaced by auth.py in https://
sheyang 2015/04/14 01:30:22 Done.
43 import owners 49 import owners
44 import owners_finder 50 import owners_finder
45 import presubmit_support 51 import presubmit_support
46 import rietveld 52 import rietveld
47 import scm 53 import scm
48 import subcommand 54 import subcommand
49 import subprocess2 55 import subprocess2
50 import watchlists 56 import watchlists
51 57
52 __version__ = '1.0' 58 __version__ = '1.0'
53 59
54 DEFAULT_SERVER = 'https://codereview.appspot.com' 60 DEFAULT_SERVER = 'https://codereview.appspot.com'
55 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' 61 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
56 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' 62 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
57 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' 63 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
58 CHANGE_ID = 'Change-Id:' 64 CHANGE_ID = 'Change-Id:'
59 REFS_THAT_ALIAS_TO_OTHER_REFS = { 65 REFS_THAT_ALIAS_TO_OTHER_REFS = {
60 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', 66 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
61 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', 67 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
62 } 68 }
63 69
70 # Buildbucket-related constants
71 DISCOVERY_URL = (
72 'https://cr-buildbucket.appspot.com/_ah/api/discovery/v1/apis/'
73 '{api}/{apiVersion}/rest')
74 DEFAULT_SCOPES = 'email'
Vadim Sh. 2015/04/10 02:24:29 not used
sheyang 2015/04/14 01:30:22 Removed.
75 BUILDSET_STR = 'patch/rietveld/{hostname}/{issue}/{patch}'
76
64 # Valid extensions for files we want to lint. 77 # Valid extensions for files we want to lint.
65 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" 78 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
66 DEFAULT_LINT_IGNORE_REGEX = r"$^" 79 DEFAULT_LINT_IGNORE_REGEX = r"$^"
67 80
68 # Shortcut since it quickly becomes redundant. 81 # Shortcut since it quickly becomes redundant.
69 Fore = colorama.Fore 82 Fore = colorama.Fore
70 83
71 # Initialized in main() 84 # Initialized in main()
72 settings = None 85 settings = None
73 86
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
208 if dirty: 221 if dirty:
209 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd 222 print 'Cannot %s with a dirty tree. You must commit locally first.' % cmd
210 print 'Uncommitted files: (git diff-index --name-status HEAD)' 223 print 'Uncommitted files: (git diff-index --name-status HEAD)'
211 print dirty[:4096] 224 print dirty[:4096]
212 if len(dirty) > 4096: 225 if len(dirty) > 4096:
213 print '... (run "git diff-index --name-status HEAD" to see full output).' 226 print '... (run "git diff-index --name-status HEAD" to see full output).'
214 return True 227 return True
215 return False 228 return False
216 229
217 230
231 def _prefix_master(master):
232 prefix = 'master.'
233 if master.startswith(prefix):
234 return master
235 else:
236 return '%s%s' % (prefix, master)
237
238
239 def _get_buildbucket(credentials):
Vadim Sh. 2015/04/10 02:24:29 with auth.py it would be: def _get_buildbucket(ri
sheyang 2015/04/14 01:30:22 Done.
240 http = httplib2.Http()
241 http = credentials.authorize(http)
242 return apiclient.discovery.build(
243 'buildbucket', 'v1',
244 http=http,
245 discoveryServiceUrl=DISCOVERY_URL,
246 )
247
248
249 def trigger_distributed_try_jobs(
250 credentials, changelist, options, masters, category):
251 buildbucket = _get_buildbucket(credentials)
252 creds_props = json.loads(credentials.to_json())
253 requester = creds_props['id_token']['email']
Vadim Sh. 2015/04/10 02:24:29 this is currently not exposed via auth.py, though
sheyang 2015/04/14 01:30:22 This field will be populated from oauth2 credentia
254 issue_props = changelist.GetIssueProperties()
255 rietveld_host = urlparse.urlparse(changelist.GetRietveldServer()).hostname
256 issue = changelist.GetIssue()
257 patchset = changelist.GetMostRecentPatchset()
258 buildset = BUILDSET_STR.format(
259 hostname=rietveld_host,
260 issue=str(issue),
261 patch=str(patchset))
262 print 'Tried jobs on:'
263 for (master, builders_and_tests) in masters.iteritems():
264 print 'Master: %s' % master
265 bucket = _prefix_master(master)
266 for builder, tests in builders_and_tests.iteritems():
267 req = buildbucket.put(body={
268 'bucket': bucket,
269 'parameters_json': json.dumps({
270 'builder_name': builder,
271 'changes':[
272 {'author': {'email': issue_props['owner_email']}},
273 ],
274 'properties': {
275 'category': category,
276 'clobber': options.clobber,
277 'issue': issue,
278 'master': master,
279 'patch_project': issue_props['project'],
280 'patch_storage': 'rietveld',
281 'patchset': patchset,
282 'reason': options.name,
283 'requester': requester,
Vadim Sh. 2015/04/10 02:24:29 the server should derive it from oauth credentials
nodir 2015/04/10 02:31:29 buidbucket does not read this property. It is inte
nodir 2015/04/13 16:04:53 This part was wrong because CQ sets requester to "
sheyang 2015/04/14 01:30:22 Acknowledged.
284 'revision': options.revision,
285 'rietveld': changelist.GetRietveldServer(),
286 'testfilter': tests,
287 },
288 }),
289 'tags': ['buildset:%s' % buildset,
290 'master:%s' % master,
291 'builder:%s' % builder,
292 'requester:%s' % requester]
nodir 2015/04/13 16:04:53 requester tag is not the user, but a tool. Maybe w
sheyang 2015/04/14 01:30:22 Use 'user_agent' instead.
293 })
294 wait = 1
295 try_count = 3
296 while try_count > 0:
297 try:
298 try_count -= 1
299 response = req.execute()
300 if response.get('error'):
301 msg = 'Error in response. Reason: %s. Message: %s.' % (
302 response['error'].get('reason', ''),
303 response['error'].get('message', ''))
304 raise BuildbucketResponseException(msg)
305 break
306 except apiclient.errors.HttpError as ex:
307 status = ex.resp.status if ex.resp else None
308 if status >= 500:
309 logging.debug('Transient errors when triggering tryjobs. '
310 'Will retry in %d seconds.', wait)
311 time.sleep(wait)
312 wait *= 2
313 if try_count <= 0:
314 raise
315 else:
316 raise
317 print ' %s: %s' % (builder, tests)
318
319
218 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): 320 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
219 """Return the corresponding git ref if |base_url| together with |glob_spec| 321 """Return the corresponding git ref if |base_url| together with |glob_spec|
220 matches the full |url|. 322 matches the full |url|.
221 323
222 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). 324 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
223 """ 325 """
224 fetch_suburl, as_ref = glob_spec.split(':') 326 fetch_suburl, as_ref = glob_spec.split(':')
225 if allow_wildcards: 327 if allow_wildcards:
226 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) 328 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
227 if glob_match: 329 if glob_match:
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 try: 377 try:
276 stdout = sys.stdout.fileno() 378 stdout = sys.stdout.fileno()
277 except AttributeError: 379 except AttributeError:
278 stdout = None 380 stdout = None
279 return subprocess2.call( 381 return subprocess2.call(
280 ['git', 382 ['git',
281 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, 383 'diff', '--no-ext-diff', '--stat'] + similarity_options + args,
282 stdout=stdout, env=env) 384 stdout=stdout, env=env)
283 385
284 386
387 class BuildbucketResponseException(Exception):
388 pass
389
390
285 class Settings(object): 391 class Settings(object):
286 def __init__(self): 392 def __init__(self):
287 self.default_server = None 393 self.default_server = None
288 self.cc = None 394 self.cc = None
289 self.root = None 395 self.root = None
290 self.is_git_svn = None 396 self.is_git_svn = None
291 self.svn_branch = None 397 self.svn_branch = None
292 self.tree_status_url = None 398 self.tree_status_url = None
293 self.viewvc_url = None 399 self.viewvc_url = None
294 self.updated = False 400 self.updated = False
(...skipping 2434 matching lines...) Expand 10 before | Expand all | Expand 10 after
2729 "-c", "--clobber", action="store_true", default=False, 2835 "-c", "--clobber", action="store_true", default=False,
2730 help="Force a clobber before building; e.g. don't do an " 2836 help="Force a clobber before building; e.g. don't do an "
2731 "incremental build") 2837 "incremental build")
2732 group.add_option( 2838 group.add_option(
2733 "--project", 2839 "--project",
2734 help="Override which project to use. Projects are defined " 2840 help="Override which project to use. Projects are defined "
2735 "server-side to define what default bot set to use") 2841 "server-side to define what default bot set to use")
2736 group.add_option( 2842 group.add_option(
2737 "-n", "--name", help="Try job name; default to current branch name") 2843 "-n", "--name", help="Try job name; default to current branch name")
2738 parser.add_option_group(group) 2844 parser.add_option_group(group)
2845 oauth2.add_oauth2_options(parser)
2739 options, args = parser.parse_args(args) 2846 options, args = parser.parse_args(args)
2740 2847
2741 if args: 2848 if args:
2742 parser.error('Unknown arguments: %s' % args) 2849 parser.error('Unknown arguments: %s' % args)
2743 2850
2744 cl = Changelist() 2851 cl = Changelist()
2745 if not cl.GetIssue(): 2852 if not cl.GetIssue():
2746 parser.error('Need to upload first') 2853 parser.error('Need to upload first')
2747 2854
2748 props = cl.GetIssueProperties() 2855 props = cl.GetIssueProperties()
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
2824 'Bot list: %s' % builders) 2931 'Bot list: %s' % builders)
2825 return 1 2932 return 1
2826 2933
2827 patchset = cl.GetMostRecentPatchset() 2934 patchset = cl.GetMostRecentPatchset()
2828 if patchset and patchset != cl.GetPatchset(): 2935 if patchset and patchset != cl.GetPatchset():
2829 print( 2936 print(
2830 '\nWARNING Mismatch between local config and server. Did a previous ' 2937 '\nWARNING Mismatch between local config and server. Did a previous '
2831 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' 2938 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2832 'Continuing using\npatchset %s.\n' % patchset) 2939 'Continuing using\npatchset %s.\n' % patchset)
2833 try: 2940 try:
2834 cl.RpcServer().trigger_distributed_try_jobs( 2941 creds = oauth2.get_oauth2_creds(
2835 cl.GetIssue(), patchset, options.name, options.clobber, 2942 options,
2836 options.revision, masters) 2943 urlparse.urlparse(cl.GetRietveldServer()).hostname)
2837 except urllib2.HTTPError, e: 2944 if not creds:
2838 if e.code == 404: 2945 print 'Failed to fetch credentials. Aborting...'
2839 print('404 from rietveld; '
2840 'did you mean to use "git try" instead of "git cl try"?')
2841 return 1 2946 return 1
2842 print('Tried jobs on:') 2947 trigger_distributed_try_jobs(creds, cl, options, masters, 'git cl try')
2843 2948 except apiclient.errors.HttpError as ex:
2844 for (master, builders) in masters.iteritems(): 2949 status = ex.resp.status if ex.resp else None
2845 if master: 2950 if status == httplib.FORBIDDEN:
2846 print 'Master: %s' % master 2951 print 'ERROR: Access denied. Please verify you have tryjob access.'
2847 length = max(len(builder) for builder in builders) 2952 else:
2848 for builder in sorted(builders): 2953 print 'ERROR: Tryjob request failed: %s.' % ex
2849 print ' %*s: %s' % (length, builder, ','.join(builders[builder])) 2954 return 1
2955 except BuildbucketResponseException as ex:
2956 print ex
2957 return 1
2958 except Exception as e:
2959 print 'Unexpected error when trying to trigger tryjobs: %s.' % e
2960 return 1
2850 return 0 2961 return 0
2851 2962
2852 2963
2853 @subcommand.usage('[new upstream branch]') 2964 @subcommand.usage('[new upstream branch]')
2854 def CMDupstream(parser, args): 2965 def CMDupstream(parser, args):
2855 """Prints or sets the name of the upstream branch, if any.""" 2966 """Prints or sets the name of the upstream branch, if any."""
2856 _, args = parser.parse_args(args) 2967 _, args = parser.parse_args(args)
2857 if len(args) > 1: 2968 if len(args) > 1:
2858 parser.error('Unrecognized args: %s' % ' '.join(args)) 2969 parser.error('Unrecognized args: %s' % ' '.join(args))
2859 2970
(...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after
3161 if __name__ == '__main__': 3272 if __name__ == '__main__':
3162 # These affect sys.stdout so do it outside of main() to simplify mocks in 3273 # These affect sys.stdout so do it outside of main() to simplify mocks in
3163 # unit testing. 3274 # unit testing.
3164 fix_encoding.fix_encoding() 3275 fix_encoding.fix_encoding()
3165 colorama.init() 3276 colorama.init()
3166 try: 3277 try:
3167 sys.exit(main(sys.argv[1:])) 3278 sys.exit(main(sys.argv[1:]))
3168 except KeyboardInterrupt: 3279 except KeyboardInterrupt:
3169 sys.stderr.write('interrupted\n') 3280 sys.stderr.write('interrupted\n')
3170 sys.exit(1) 3281 sys.exit(1)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698