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

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: rebase Created 5 years, 7 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 time
26 import traceback
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
35 from third_party import colorama 37 from third_party import colorama
38 from third_party import httplib2
36 from third_party import upload 39 from third_party import upload
37 import auth 40 import auth
38 import breakpad # pylint: disable=W0611 41 import breakpad # pylint: disable=W0611
39 import clang_format 42 import clang_format
40 import dart_format 43 import dart_format
41 import fix_encoding 44 import fix_encoding
42 import gclient_utils 45 import gclient_utils
43 import git_common 46 import git_common
44 import owners 47 import owners
45 import owners_finder 48 import owners_finder
46 import presubmit_support 49 import presubmit_support
47 import rietveld 50 import rietveld
48 import scm 51 import scm
49 import subcommand 52 import subcommand
50 import subprocess2 53 import subprocess2
51 import watchlists 54 import watchlists
52 55
53 __version__ = '1.0' 56 __version__ = '1.0'
54 57
55 DEFAULT_SERVER = 'https://codereview.appspot.com' 58 DEFAULT_SERVER = 'https://codereview.appspot.com'
56 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s' 59 POSTUPSTREAM_HOOK_PATTERN = '.git/hooks/post-cl-%s'
57 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' 60 DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup'
58 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit' 61 GIT_INSTRUCTIONS_URL = 'http://code.google.com/p/chromium/wiki/UsingGit'
59 CHANGE_ID = 'Change-Id:' 62 CHANGE_ID = 'Change-Id:'
60 REFS_THAT_ALIAS_TO_OTHER_REFS = { 63 REFS_THAT_ALIAS_TO_OTHER_REFS = {
61 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master', 64 'refs/remotes/origin/lkgr': 'refs/remotes/origin/master',
62 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master', 65 'refs/remotes/origin/lkcr': 'refs/remotes/origin/master',
63 } 66 }
64 67
68 # Buildbucket-related constants
69 BUILDBUCKET_PUT_URL = (
M-A Ruel 2015/05/12 00:46:21 Please only code the host here. Host != API, so th
sheyang 2015/05/12 01:37:56 Done.
70 'https://cr-buildbucket.appspot.com/_ah/api/buildbucket/v1/builds/batch')
M-A Ruel 2015/05/11 22:21:28 That URL shouldn't be hardcoded there.
nodir 2015/05/11 22:48:29 I think hardcoding the default hostname is fine, b
M-A Ruel 2015/05/11 23:01:27 Not lgtm. It's not optional nor nice to have.
sheyang 2015/05/12 01:37:56 Done.
71 BUILDSET_STR = 'patch/rietveld/{hostname}/{issue}/{patch}'
72
65 # Valid extensions for files we want to lint. 73 # Valid extensions for files we want to lint.
66 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)" 74 DEFAULT_LINT_REGEX = r"(.*\.cpp|.*\.cc|.*\.h)"
67 DEFAULT_LINT_IGNORE_REGEX = r"$^" 75 DEFAULT_LINT_IGNORE_REGEX = r"$^"
68 76
69 # Shortcut since it quickly becomes redundant. 77 # Shortcut since it quickly becomes redundant.
70 Fore = colorama.Fore 78 Fore = colorama.Fore
71 79
72 # Initialized in main() 80 # Initialized in main()
73 settings = None 81 settings = None
74 82
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
195 else: 203 else:
196 git_set_branch_value('git-find-copies', int(options.find_copies)) 204 git_set_branch_value('git-find-copies', int(options.find_copies))
197 205
198 print('Using %d%% similarity for rename/copy detection. ' 206 print('Using %d%% similarity for rename/copy detection. '
199 'Override with --similarity.' % options.similarity) 207 'Override with --similarity.' % options.similarity)
200 208
201 return options, args 209 return options, args
202 parser.parse_args = Parse 210 parser.parse_args = Parse
203 211
204 212
213 def _prefix_master(master):
M-A Ruel 2015/05/12 00:46:21 Why?
sheyang 2015/05/12 01:37:56 See the comments about 'black magic' below.
214 prefix = 'master.'
215 if master.startswith(prefix):
216 return master
217 else:
218 return '%s%s' % (prefix, master)
219
220
221 def trigger_try_jobs(
222 auth_config, changelist, options, masters, category):
223 rietveld_url = settings.GetDefaultServerUrl()
224 rietveld_host = urlparse.urlparse(rietveld_url).hostname
225 authenticator = auth.get_authenticator_for_host(rietveld_host, auth_config)
226 http = authenticator.authorize(httplib2.Http())
227 http.force_exception_to_status_code = True
228 issue_props = changelist.GetIssueProperties()
229 issue = changelist.GetIssue()
230 patchset = changelist.GetMostRecentPatchset()
231 buildset = BUILDSET_STR.format(
232 hostname=rietveld_host,
233 issue=issue,
234 patch=patchset)
235
236 batch_req_body = {'builds': []}
237 print_text = []
238 print_text.append('Tried jobs on:')
M-A Ruel 2015/05/11 23:13:59 Why not printing as it's happening? Why hang outpu
sheyang 2015/05/12 01:37:56 Here we're only preparing parameters for the PUT c
239 for master, builders_and_tests in masters.iteritems():
M-A Ruel 2015/05/11 23:13:59 Please sort.
sheyang 2015/05/12 01:37:56 Done.
240 print_text.append('Master: %s' % master)
241 bucket = _prefix_master(master)
M-A Ruel 2015/05/12 00:46:21 This looks like black magic, explain the context.
sheyang 2015/05/12 01:37:56 Buildbucket uses full master name as bucket name '
M-A Ruel 2015/05/12 13:05:27 Please add this to _prefix_master docstring then.
sheyang 2015/05/12 17:40:38 Done.
242 for builder, tests in builders_and_tests.iteritems():
M-A Ruel 2015/05/12 00:46:21 sort this too.
sheyang 2015/05/12 01:37:56 Done.
243 print_text.append(' %s: %s' % (builder, tests))
244 parameters = {
245 'builder_name': builder,
246 'changes':[
247 {'author': {'email': issue_props['owner_email']}},
248 ],
249 'properties': {
250 'category': category,
251 'issue': issue,
252 'master': master,
253 'patch_project': issue_props['project'],
254 'patch_storage': 'rietveld',
255 'patchset': patchset,
256 'reason': options.name,
257 'revision': options.revision,
258 'rietveld': rietveld_url,
259 'testfilter': tests,
260 },
261 }
262 if options.clobber:
263 parameters['properties']['clobber'] = True
264 batch_req_body['builds'].append(
265 {
266 'bucket': bucket,
267 'parameters_json': json.dumps(parameters),
M-A Ruel 2015/05/12 00:46:21 What. You are encoding json inside json? Why? Beca
sheyang 2015/05/12 01:37:56 Yes this parameter property is for buildbot about
268 'tags': ['builder:%s' % builder,
269 'buildset:%s' % buildset,
270 'master:%s' % master,
271 'user_agent:git_cl_try']
272 }
273 )
274
275 wait = 1
276 try_count = 3
277 while try_count > 0:
278 try_count -= 1
M-A Ruel 2015/05/11 23:13:59 try_count--
nodir 2015/05/11 23:19:36 this is invalid Python syntax
M-A Ruel 2015/05/11 23:20:22 Eh, damn python.
sheyang 2015/05/12 01:37:56 Acknowledged.
279 response, content = http.request(
280 BUILDBUCKET_PUT_URL,
281 'PUT',
M-A Ruel 2015/05/11 23:01:27 If I understand correctly, the sole reason http is
nodir 2015/05/11 23:19:36 to authenticate the request.
sheyang 2015/05/12 01:37:56 Discussed with maruel@ offline and the recommended
282 body=json.dumps(batch_req_body),
283 headers={'Content-Type': 'application/json'},
284 )
285 content_json = None
286 try:
287 content_json = json.loads(content)
288 except ValueError:
289 pass
290
291 # Buildbucket could return an error even if status==200.
292 if content_json and content_json.get('error'):
293 msg = 'Error in response. Code: %d. Reason: %s. Message: %s.' % (
294 content_json['error'].get('code', ''),
295 content_json['error'].get('reason', ''),
296 content_json['error'].get('message', ''))
297 raise BuildbucketResponseException(msg)
298
299 if response.status == 200:
300 break
301
302 if response.status < 500 or try_count <= 0:
303 raise httplib2.HttpLib2Error(content)
304
305 # status >= 500 means transient failures.
306 logging.debug('Transient errors when triggering tryjobs. '
307 'Will retry in %d seconds.', wait)
308 time.sleep(wait)
Vadim Sh. 2015/05/11 22:14:01 nit: no need to wait if it was the last attempt (i
M-A Ruel 2015/05/11 23:13:59 Please address this.
sheyang 2015/05/12 01:37:56 if try_count <=0, we'll raise an exception above s
309 wait *= 2
310
311 print '\n'.join(print_text)
312
313
205 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards): 314 def MatchSvnGlob(url, base_url, glob_spec, allow_wildcards):
206 """Return the corresponding git ref if |base_url| together with |glob_spec| 315 """Return the corresponding git ref if |base_url| together with |glob_spec|
207 matches the full |url|. 316 matches the full |url|.
208 317
209 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below). 318 If |allow_wildcards| is true, |glob_spec| can contain wildcards (see below).
210 """ 319 """
211 fetch_suburl, as_ref = glob_spec.split(':') 320 fetch_suburl, as_ref = glob_spec.split(':')
212 if allow_wildcards: 321 if allow_wildcards:
213 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl) 322 glob_match = re.match('(.+/)?(\*|{[^/]*})(/.+)?', fetch_suburl)
214 if glob_match: 323 if glob_match:
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
262 try: 371 try:
263 stdout = sys.stdout.fileno() 372 stdout = sys.stdout.fileno()
264 except AttributeError: 373 except AttributeError:
265 stdout = None 374 stdout = None
266 return subprocess2.call( 375 return subprocess2.call(
267 ['git', 376 ['git',
268 'diff', '--no-ext-diff', '--stat'] + similarity_options + args, 377 'diff', '--no-ext-diff', '--stat'] + similarity_options + args,
269 stdout=stdout, env=env) 378 stdout=stdout, env=env)
270 379
271 380
381 class BuildbucketResponseException(Exception):
382 pass
383
384
272 class Settings(object): 385 class Settings(object):
273 def __init__(self): 386 def __init__(self):
274 self.default_server = None 387 self.default_server = None
275 self.cc = None 388 self.cc = None
276 self.root = None 389 self.root = None
277 self.is_git_svn = None 390 self.is_git_svn = None
278 self.svn_branch = None 391 self.svn_branch = None
279 self.tree_status_url = None 392 self.tree_status_url = None
280 self.viewvc_url = None 393 self.viewvc_url = None
281 self.updated = False 394 self.updated = False
(...skipping 2475 matching lines...) Expand 10 before | Expand all | Expand 10 after
2757 group.add_option( 2870 group.add_option(
2758 "-c", "--clobber", action="store_true", default=False, 2871 "-c", "--clobber", action="store_true", default=False,
2759 help="Force a clobber before building; e.g. don't do an " 2872 help="Force a clobber before building; e.g. don't do an "
2760 "incremental build") 2873 "incremental build")
2761 group.add_option( 2874 group.add_option(
2762 "--project", 2875 "--project",
2763 help="Override which project to use. Projects are defined " 2876 help="Override which project to use. Projects are defined "
2764 "server-side to define what default bot set to use") 2877 "server-side to define what default bot set to use")
2765 group.add_option( 2878 group.add_option(
2766 "-n", "--name", help="Try job name; default to current branch name") 2879 "-n", "--name", help="Try job name; default to current branch name")
2880 group.add_option(
2881 "--use-buildbucket", action="store_true", default=False,
2882 help="Use buildbucket to trigger try jobs.")
2767 parser.add_option_group(group) 2883 parser.add_option_group(group)
2768 auth.add_auth_options(parser) 2884 auth.add_auth_options(parser)
2769 options, args = parser.parse_args(args) 2885 options, args = parser.parse_args(args)
2770 auth_config = auth.extract_auth_config_from_options(options) 2886 auth_config = auth.extract_auth_config_from_options(options)
2771 2887
2772 if args: 2888 if args:
2773 parser.error('Unknown arguments: %s' % args) 2889 parser.error('Unknown arguments: %s' % args)
2774 2890
2775 cl = Changelist(auth_config=auth_config) 2891 cl = Changelist(auth_config=auth_config)
2776 if not cl.GetIssue(): 2892 if not cl.GetIssue():
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
2854 'Instead send your job to the parent.\n' 2970 'Instead send your job to the parent.\n'
2855 'Bot list: %s' % builders) 2971 'Bot list: %s' % builders)
2856 return 1 2972 return 1
2857 2973
2858 patchset = cl.GetMostRecentPatchset() 2974 patchset = cl.GetMostRecentPatchset()
2859 if patchset and patchset != cl.GetPatchset(): 2975 if patchset and patchset != cl.GetPatchset():
2860 print( 2976 print(
2861 '\nWARNING Mismatch between local config and server. Did a previous ' 2977 '\nWARNING Mismatch between local config and server. Did a previous '
2862 'upload fail?\ngit-cl try always uses latest patchset from rietveld. ' 2978 'upload fail?\ngit-cl try always uses latest patchset from rietveld. '
2863 'Continuing using\npatchset %s.\n' % patchset) 2979 'Continuing using\npatchset %s.\n' % patchset)
2864 try: 2980 if options.use_buildbucket:
2865 cl.RpcServer().trigger_distributed_try_jobs( 2981 try:
2866 cl.GetIssue(), patchset, options.name, options.clobber, 2982 trigger_try_jobs(
2867 options.revision, masters) 2983 auth_config, cl, options, masters, 'git_cl_try')
2868 except urllib2.HTTPError, e: 2984 except BuildbucketResponseException as ex:
2869 if e.code == 404: 2985 print 'ERROR: %s' % ex
2870 print('404 from rietveld; '
2871 'did you mean to use "git try" instead of "git cl try"?')
2872 return 1 2986 return 1
2873 print('Tried jobs on:') 2987 except Exception as e:
2988 stacktrace = (''.join(traceback.format_stack()) + traceback.format_exc())
2989 print 'ERROR: Exception when trying to trigger tryjobs: %s\n%s' % (
2990 e, stacktrace)
2991 return 1
2992 else:
2993 try:
2994 cl.RpcServer().trigger_distributed_try_jobs(
2995 cl.GetIssue(), patchset, options.name, options.clobber,
2996 options.revision, masters)
2997 except urllib2.HTTPError, e:
2998 if e.code == 404:
2999 print('404 from rietveld; '
3000 'did you mean to use "git try" instead of "git cl try"?')
3001 return 1
3002 print('Tried jobs on:')
2874 3003
2875 for (master, builders) in masters.iteritems(): 3004 for (master, builders) in masters.iteritems():
2876 if master: 3005 if master:
2877 print 'Master: %s' % master 3006 print 'Master: %s' % master
2878 length = max(len(builder) for builder in builders) 3007 length = max(len(builder) for builder in builders)
2879 for builder in sorted(builders): 3008 for builder in sorted(builders):
2880 print ' %*s: %s' % (length, builder, ','.join(builders[builder])) 3009 print ' %*s: %s' % (length, builder, ','.join(builders[builder]))
2881 return 0 3010 return 0
2882 3011
2883 3012
2884 @subcommand.usage('[new upstream branch]') 3013 @subcommand.usage('[new upstream branch]')
2885 def CMDupstream(parser, args): 3014 def CMDupstream(parser, args):
2886 """Prints or sets the name of the upstream branch, if any.""" 3015 """Prints or sets the name of the upstream branch, if any."""
2887 _, args = parser.parse_args(args) 3016 _, args = parser.parse_args(args)
2888 if len(args) > 1: 3017 if len(args) > 1:
2889 parser.error('Unrecognized args: %s' % ' '.join(args)) 3018 parser.error('Unrecognized args: %s' % ' '.join(args))
2890 3019
(...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after
3205 if __name__ == '__main__': 3334 if __name__ == '__main__':
3206 # These affect sys.stdout so do it outside of main() to simplify mocks in 3335 # These affect sys.stdout so do it outside of main() to simplify mocks in
3207 # unit testing. 3336 # unit testing.
3208 fix_encoding.fix_encoding() 3337 fix_encoding.fix_encoding()
3209 colorama.init() 3338 colorama.init()
3210 try: 3339 try:
3211 sys.exit(main(sys.argv[1:])) 3340 sys.exit(main(sys.argv[1:]))
3212 except KeyboardInterrupt: 3341 except KeyboardInterrupt:
3213 sys.stderr.write('interrupted\n') 3342 sys.stderr.write('interrupted\n')
3214 sys.exit(1) 3343 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