 Chromium Code Reviews
 Chromium Code Reviews Issue 254133007:
  trychange.py: Gerrit protocol  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
    
  
    Issue 254133007:
  trychange.py: Gerrit protocol  (Closed) 
  Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools| OLD | NEW | 
|---|---|
| 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 """Client-side script to send a try job to the try server. It communicates to | 6 """Client-side script to send a try job to the try server. It communicates to | 
| 7 the try server by either writting to a svn/git repository or by directly | 7 the try server by either writting to a svn/git repository or by directly | 
| 8 connecting to the server by HTTP. | 8 connecting to the server by HTTP. | 
| 9 """ | 9 """ | 
| 10 | 10 | 
| 11 import contextlib | 11 import contextlib | 
| 12 import datetime | 12 import datetime | 
| 13 import errno | 13 import errno | 
| 14 import getpass | 14 import getpass | 
| 15 import itertools | 15 import itertools | 
| 16 import json | 16 import json | 
| 17 import logging | 17 import logging | 
| 18 import optparse | 18 import optparse | 
| 19 import os | 19 import os | 
| 20 import posixpath | 20 import posixpath | 
| 21 import re | 21 import re | 
| 22 import shutil | 22 import shutil | 
| 23 import sys | 23 import sys | 
| 24 import tempfile | 24 import tempfile | 
| 25 import urllib | 25 import urllib | 
| 26 import urllib2 | 26 import urllib2 | 
| 27 import urlparse | |
| 27 | 28 | 
| 28 import breakpad # pylint: disable=W0611 | 29 import breakpad # pylint: disable=W0611 | 
| 29 | 30 | 
| 31 import fix_encoding | |
| 30 import gcl | 32 import gcl | 
| 31 import fix_encoding | |
| 32 import gclient_utils | 33 import gclient_utils | 
| 34 import gerrit_util | |
| 33 import scm | 35 import scm | 
| 34 import subprocess2 | 36 import subprocess2 | 
| 35 | 37 | 
| 36 | 38 | 
| 37 __version__ = '1.2' | 39 __version__ = '1.2' | 
| 38 | 40 | 
| 39 | 41 | 
| 40 # Constants | 42 # Constants | 
| 41 HELP_STRING = "Sorry, Tryserver is not available." | 43 HELP_STRING = "Sorry, Tryserver is not available." | 
| 42 USAGE = r"""%prog [options] | 44 USAGE = r"""%prog [options] | 
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 88 DieWithError( | 90 DieWithError( | 
| 89 'Command "%s" failed.\n%s' % ( | 91 'Command "%s" failed.\n%s' % ( | 
| 90 ' '.join(args), error_message or e.stdout or '')) | 92 ' '.join(args), error_message or e.stdout or '')) | 
| 91 return e.stdout | 93 return e.stdout | 
| 92 | 94 | 
| 93 | 95 | 
| 94 def RunGit(args, **kwargs): | 96 def RunGit(args, **kwargs): | 
| 95 """Returns stdout.""" | 97 """Returns stdout.""" | 
| 96 return RunCommand(['git'] + args, **kwargs) | 98 return RunCommand(['git'] + args, **kwargs) | 
| 97 | 99 | 
| 100 class Error(Exception): | |
| 101 """An error during a try job submission. | |
| 98 | 102 | 
| 99 class InvalidScript(Exception): | 103 For this error, trychange.py does not display stack trace, only message | 
| 104 """ | |
| 105 | |
| 106 class InvalidScript(Error): | |
| 100 def __str__(self): | 107 def __str__(self): | 
| 101 return self.args[0] + '\n' + HELP_STRING | 108 return self.args[0] + '\n' + HELP_STRING | 
| 102 | 109 | 
| 103 | 110 | 
| 104 class NoTryServerAccess(Exception): | 111 class NoTryServerAccess(Error): | 
| 105 def __str__(self): | 112 def __str__(self): | 
| 106 return self.args[0] + '\n' + HELP_STRING | 113 return self.args[0] + '\n' + HELP_STRING | 
| 107 | 114 | 
| 108 | |
| 109 def Escape(name): | 115 def Escape(name): | 
| 110 """Escapes characters that could interfere with the file system or try job | 116 """Escapes characters that could interfere with the file system or try job | 
| 111 parsing. | 117 parsing. | 
| 112 """ | 118 """ | 
| 113 return re.sub(r'[^\w#-]', '_', name) | 119 return re.sub(r'[^\w#-]', '_', name) | 
| 114 | 120 | 
| 115 | 121 | 
| 116 class SCM(object): | 122 class SCM(object): | 
| 117 """Simplistic base class to implement one function: ProcessOptions.""" | 123 """Simplistic base class to implement one function: ProcessOptions.""" | 
| 118 def __init__(self, options, path, file_list): | 124 def __init__(self, options, path, file_list): | 
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 161 """Set default settings based on the gcl-style settings from the repository. | 167 """Set default settings based on the gcl-style settings from the repository. | 
| 162 | 168 | 
| 163 The settings in the self.options object will only be set if no previous | 169 The settings in the self.options object will only be set if no previous | 
| 164 value exists (i.e. command line flags to the try command will override the | 170 value exists (i.e. command line flags to the try command will override the | 
| 165 settings in codereview.settings). | 171 settings in codereview.settings). | 
| 166 """ | 172 """ | 
| 167 settings = { | 173 settings = { | 
| 168 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'), | 174 'port': self.GetCodeReviewSetting('TRYSERVER_HTTP_PORT'), | 
| 169 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'), | 175 'host': self.GetCodeReviewSetting('TRYSERVER_HTTP_HOST'), | 
| 170 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'), | 176 'svn_repo': self.GetCodeReviewSetting('TRYSERVER_SVN_URL'), | 
| 177 'gerrit_url': self.GetCodeReviewSetting('TRYSERVER_GERRIT_URL'), | |
| 171 'git_repo': self.GetCodeReviewSetting('TRYSERVER_GIT_URL'), | 178 'git_repo': self.GetCodeReviewSetting('TRYSERVER_GIT_URL'), | 
| 172 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'), | 179 'project': self.GetCodeReviewSetting('TRYSERVER_PROJECT'), | 
| 173 # Primarily for revision=auto | 180 # Primarily for revision=auto | 
| 174 'revision': self.GetCodeReviewSetting('TRYSERVER_REVISION'), | 181 'revision': self.GetCodeReviewSetting('TRYSERVER_REVISION'), | 
| 175 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), | 182 'root': self.GetCodeReviewSetting('TRYSERVER_ROOT'), | 
| 176 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), | 183 'patchlevel': self.GetCodeReviewSetting('TRYSERVER_PATCHLEVEL'), | 
| 177 } | 184 } | 
| 178 logging.info('\n'.join(['%s: %s' % (k, v) | 185 logging.info('\n'.join(['%s: %s' % (k, v) | 
| 179 for (k, v) in settings.iteritems() if v])) | 186 for (k, v) in settings.iteritems() if v])) | 
| 180 for (k, v) in settings.iteritems(): | 187 for (k, v) in settings.iteritems(): | 
| (...skipping 518 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 699 # Fetch, reset, update branch file again. | 706 # Fetch, reset, update branch file again. | 
| 700 patch_git('fetch', 'origin') | 707 patch_git('fetch', 'origin') | 
| 701 patch_git('reset', '--hard', 'origin/master') | 708 patch_git('reset', '--hard', 'origin/master') | 
| 702 update_branch() | 709 update_branch() | 
| 703 except subprocess2.CalledProcessError, e: | 710 except subprocess2.CalledProcessError, e: | 
| 704 # Restore state. | 711 # Restore state. | 
| 705 patch_git('checkout', 'master') | 712 patch_git('checkout', 'master') | 
| 706 patch_git('reset', '--hard', 'origin/master') | 713 patch_git('reset', '--hard', 'origin/master') | 
| 707 raise | 714 raise | 
| 708 | 715 | 
| 716 def _SendChangeGerrit(bot_spec, options): | |
| 717 """Posts a try job to a Gerrit change. | |
| 718 | |
| 719 Reads Change-Id from the HEAD commit, resolves the current revision, checks | |
| 720 that local revision matches the uploaded one, posts a try job in form of a | |
| 721 message, sets Tryjob label to 1. | |
| 722 | |
| 723 Gerrit message format: starts with !tryjob, optionally followed by a tryjob | |
| 724 definition in JSON format: | |
| 725 buildNames: list of strings specifying build names. | |
| 726 """ | |
| 727 | |
| 728 logging.info('Sending by Gerrit') | |
| 729 if not options.gerrit_url: | |
| 730 raise NoTryServerAccess('Please use --gerrit_url option to specify the ' | |
| 731 'Gerrit instance url to connect to') | |
| 732 gerrit_host = urlparse.urlparse(options.gerrit_url).hostname | |
| 733 logging.debug('Gerrit host: %s' % gerrit_host) | |
| 734 | |
| 735 def get_change_id(): | |
| 
Vadim Sh.
2014/04/30 00:40:48
looks like this file is using GetChangeId naming s
 | |
| 736 """Finds Change-ID of the HEAD commit.""" | |
| 737 CHANGE_ID_RGX = '^Change-Id: (I[a-f0-9]{10,})' | |
| 738 comment = scm.GIT.Capture(['log', '-1', '--format=%b'], cwd=os.getcwd()) | |
| 739 change_id_match = re.search(CHANGE_ID_RGX, comment, re.I | re.M) | |
| 740 if not change_id_match: | |
| 741 raise Error('Change-Id was not found in the HEAD commit.') | |
| 
Vadim Sh.
2014/04/30 00:40:48
It would be helpful to explain what to do in that
 | |
| 742 change_id = change_id_match.group(1) | |
| 743 assert change_id and change_id.startswith('I') | |
| 
Vadim Sh.
2014/04/30 00:40:48
I think this assert is redundant since your regexp
 | |
| 744 return change_id | |
| 745 | |
| 746 def format_message(): | |
| 747 # Build job definition. | |
| 748 job_def = {} | |
| 749 builderNames = [builder for builder, _ in bot_spec] | |
| 750 if builderNames: | |
| 751 job_def['builderNames'] = builderNames | |
| 752 | |
| 753 # Format message. | |
| 754 msg = '!tryjob' | |
| 755 if job_def: | |
| 756 msg = '%s %s' % (msg, json.dumps(job_def)) | |
| 
Vadim Sh.
2014/04/30 00:40:48
json.dumps(job_def, sort_keys=True)
 | |
| 757 return msg | |
| 
Vadim Sh.
2014/04/30 00:40:48
Does Gerrit append '\n' for you here? I think it's
 
nodir
2014/04/30 18:57:27
The regexp expects EOL or EOF  after job definitio
 | |
| 758 | |
| 759 head_sha = scm.GIT.Capture(['log', '-1', '--format=%H'], cwd=os.getcwd()) | |
| 760 | |
| 761 change_id = get_change_id() | |
| 
Vadim Sh.
2014/04/30 00:40:48
I think it would be cleaner to pass |head_sha| to
 
nodir
2014/04/30 18:57:27
Done.
 | |
| 762 assert change_id | |
| 
Vadim Sh.
2014/04/30 00:40:48
I think this is redundant too..
 | |
| 763 | |
| 764 # Check that the uploaded revision matches the local one. | |
| 765 changes = gerrit_util.GetChangeCurrentRevision(gerrit_host, change_id) | |
| 766 assert len(changes) in (0, 1), 'Multiple changes with id %s' % change_id | |
| 
Vadim Sh.
2014/04/30 00:40:48
hm.. len(changes) <= 1?
 
nodir
2014/04/30 18:57:27
Wanted to be more explicit, it's probably looks we
 | |
| 767 if not changes: | |
| 768 raise Error('A change %s was not found on the server. Was it uploaded?' % | |
| 769 change_id) | |
| 770 logging.debug('Found Gerrit change: %s' % changes[0]) | |
| 771 if changes[0]['current_revision'] != head_sha: | |
| 772 raise Error('Some changes were made since last upload to Gerrit, ' | |
| 
Vadim Sh.
2014/04/30 00:40:48
I'd rephrase it somehow. Maybe "Please reupload yo
 
nodir
2014/04/30 18:57:27
Modified the string a bit.
 | |
| 773 'so stopping.') | |
| 774 | |
| 775 # Post a try job. | |
| 776 message = format_message() | |
| 777 logging.info('Posting gerrit message: %s' % message) | |
| 778 if not options.dry_run: | |
| 779 # Post a message and set TryJob=1 label | |
| 
Vadim Sh.
2014/04/30 00:40:48
nit: append '.' at the end of a sentence :)
 | |
| 780 gerrit_util.SetReview(gerrit_host, change_id, msg=message, | |
| 781 labels={'Tryjob': 1}) | |
| 
Vadim Sh.
2014/04/30 00:40:48
What happens here if project doesn't have Tryjob l
 
nodir
2014/04/30 18:57:27
It will report:
Bad Request: label "Tryjb" is not
 | |
| 782 | |
| 709 | 783 | 
| 710 def PrintSuccess(bot_spec, options): | 784 def PrintSuccess(bot_spec, options): | 
| 711 if not options.dry_run: | 785 if not options.dry_run: | 
| 712 text = 'Patch \'%s\' sent to try server' % options.name | 786 text = 'Patch \'%s\' sent to try server' % options.name | 
| 713 if bot_spec: | 787 if bot_spec: | 
| 714 text += ': %s' % ', '.join( | 788 text += ': %s' % ', '.join( | 
| 715 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) | 789 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) | 
| 716 print(text) | 790 print(text) | 
| 717 | 791 | 
| 718 | 792 | 
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 913 group.add_option("--use_git", | 987 group.add_option("--use_git", | 
| 914 action="store_const", | 988 action="store_const", | 
| 915 const=_SendChangeGit, | 989 const=_SendChangeGit, | 
| 916 dest="send_patch", | 990 dest="send_patch", | 
| 917 help="Use GIT to talk to the try server") | 991 help="Use GIT to talk to the try server") | 
| 918 group.add_option("-G", "--git_repo", | 992 group.add_option("-G", "--git_repo", | 
| 919 metavar="GIT_URL", | 993 metavar="GIT_URL", | 
| 920 help="GIT url to use to write the changes in; --use_git is " | 994 help="GIT url to use to write the changes in; --use_git is " | 
| 921 "implied when using --git_repo") | 995 "implied when using --git_repo") | 
| 922 parser.add_option_group(group) | 996 parser.add_option_group(group) | 
| 997 | |
| 998 group = optparse.OptionGroup(parser, "Access the try server with Gerrit") | |
| 999 group.add_option("--use_gerrit", | |
| 1000 action="store_const", | |
| 1001 const=_SendChangeGerrit, | |
| 1002 dest="send_patch", | |
| 1003 help="Use Gerrit to talk to the try server") | |
| 1004 group.add_option("--gerrit_url", | |
| 1005 metavar="GERRIT_URL", | |
| 1006 help="Gerrit url to post a tryjob to; --use_gerrit is " | |
| 1007 "implied when using --gerrit_url") | |
| 1008 parser.add_option_group(group) | |
| 1009 | |
| 923 return parser | 1010 return parser | 
| 924 | 1011 | 
| 925 | 1012 | 
| 926 def TryChange(argv, | 1013 def TryChange(argv, | 
| 927 change, | 1014 change, | 
| 928 swallow_exception, | 1015 swallow_exception, | 
| 929 prog=None, | 1016 prog=None, | 
| 930 extra_epilog=None): | 1017 extra_epilog=None): | 
| 931 """ | 1018 """ | 
| 932 Args: | 1019 Args: | 
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1026 os.path.join(current_vcs.checkout_root, item), | 1113 os.path.join(current_vcs.checkout_root, item), | 
| 1027 None) | 1114 None) | 
| 1028 if checkout.checkout_root in [c.checkout_root for c in checkouts]: | 1115 if checkout.checkout_root in [c.checkout_root for c in checkouts]: | 
| 1029 parser.error('Specified the root %s two times.' % | 1116 parser.error('Specified the root %s two times.' % | 
| 1030 checkout.checkout_root) | 1117 checkout.checkout_root) | 
| 1031 checkouts.append(checkout) | 1118 checkouts.append(checkout) | 
| 1032 | 1119 | 
| 1033 can_http = options.port and options.host | 1120 can_http = options.port and options.host | 
| 1034 can_svn = options.svn_repo | 1121 can_svn = options.svn_repo | 
| 1035 can_git = options.git_repo | 1122 can_git = options.git_repo | 
| 1123 can_gerrit = options.gerrit_url | |
| 1124 can_anything = can_http or can_svn or can_git or can_gerrit | |
| 
Vadim Sh.
2014/04/30 00:40:48
I'm far from english expert, but shouldn't it be "
 
nodir
2014/04/30 18:57:27
Done
 | |
| 1036 # If there was no transport selected yet, now we must have enough data to | 1125 # If there was no transport selected yet, now we must have enough data to | 
| 1037 # select one. | 1126 # select one. | 
| 1038 if not options.send_patch and not (can_http or can_svn or can_git): | 1127 if not options.send_patch and not can_anything: | 
| 1039 parser.error('Please specify an access method.') | 1128 parser.error('Please specify an access method.') | 
| 1040 | 1129 | 
| 1041 # Convert options.diff into the content of the diff. | 1130 # Convert options.diff into the content of the diff. | 
| 1042 if options.url: | 1131 if options.url: | 
| 1043 if options.files: | 1132 if options.files: | 
| 1044 parser.error('You cannot specify files and --url at the same time.') | 1133 parser.error('You cannot specify files and --url at the same time.') | 
| 1045 options.diff = urllib2.urlopen(options.url).read() | 1134 options.diff = urllib2.urlopen(options.url).read() | 
| 1046 elif options.diff: | 1135 elif options.diff: | 
| 1047 if options.files: | 1136 if options.files: | 
| 1048 parser.error('You cannot specify files and --diff at the same time.') | 1137 parser.error('You cannot specify files and --diff at the same time.') | 
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1115 | 1204 | 
| 1116 # Determine sending protocol | 1205 # Determine sending protocol | 
| 1117 if options.send_patch: | 1206 if options.send_patch: | 
| 1118 # If forced. | 1207 # If forced. | 
| 1119 senders = [options.send_patch] | 1208 senders = [options.send_patch] | 
| 1120 else: | 1209 else: | 
| 1121 # Try sending patch using avaialble protocols | 1210 # Try sending patch using avaialble protocols | 
| 1122 all_senders = [ | 1211 all_senders = [ | 
| 1123 (_SendChangeHTTP, can_http), | 1212 (_SendChangeHTTP, can_http), | 
| 1124 (_SendChangeSVN, can_svn), | 1213 (_SendChangeSVN, can_svn), | 
| 1125 (_SendChangeGit, can_git) | 1214 (_SendChangeGerrit, can_gerrit), | 
| 1215 (_SendChangeGit, can_git), | |
| 1126 ] | 1216 ] | 
| 1127 senders = [sender for sender, can in all_senders if can] | 1217 senders = [sender for sender, can in all_senders if can] | 
| 1128 | 1218 | 
| 1129 # Send the patch. | 1219 # Send the patch. | 
| 1130 for sender in senders: | 1220 for sender in senders: | 
| 1131 try: | 1221 try: | 
| 1132 sender(bot_spec, options) | 1222 sender(bot_spec, options) | 
| 1133 PrintSuccess(bot_spec, options) | 1223 PrintSuccess(bot_spec, options) | 
| 1134 return 0 | 1224 return 0 | 
| 1135 except NoTryServerAccess: | 1225 except NoTryServerAccess: | 
| 1136 is_last = sender == senders[-1] | 1226 is_last = sender == senders[-1] | 
| 1137 if is_last: | 1227 if is_last: | 
| 1138 raise | 1228 raise | 
| 1139 assert False, "Unreachable code" | 1229 assert False, "Unreachable code" | 
| 1140 except (InvalidScript, NoTryServerAccess), e: | 1230 except Error, e: | 
| 1141 if swallow_exception: | 1231 if swallow_exception: | 
| 1142 return 1 | 1232 return 1 | 
| 1143 print >> sys.stderr, e | 1233 print >> sys.stderr, e | 
| 1144 return 1 | 1234 return 1 | 
| 1145 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1235 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 
| 1146 print >> sys.stderr, e | 1236 print >> sys.stderr, e | 
| 1147 return 1 | 1237 return 1 | 
| 1148 return 0 | 1238 return 0 | 
| 1149 | 1239 | 
| 1150 | 1240 | 
| 1151 if __name__ == "__main__": | 1241 if __name__ == "__main__": | 
| 1152 fix_encoding.fix_encoding() | 1242 fix_encoding.fix_encoding() | 
| 1153 sys.exit(TryChange(None, None, False)) | 1243 sys.exit(TryChange(None, None, False)) | 
| OLD | NEW |