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

Side by Side Diff: trychange.py

Issue 254133007: trychange.py: Gerrit protocol (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: PostTryjob Created 6 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 | « tests/trychange_unittest.py ('k') | 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 """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
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
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 303 matching lines...) Expand 10 before | Expand all | Expand 10 after
484 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, 491 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url,
485 str(e.args))) 492 str(e.args)))
486 if not connection: 493 if not connection:
487 raise NoTryServerAccess('%s is unaccessible.' % url) 494 raise NoTryServerAccess('%s is unaccessible.' % url)
488 logging.info('Reading response...') 495 logging.info('Reading response...')
489 response = connection.read() 496 response = connection.read()
490 logging.info('Done') 497 logging.info('Done')
491 if response != 'OK': 498 if response != 'OK':
492 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) 499 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response))
493 500
501 PrintSuccess(bot_spec, options)
494 502
495 @contextlib.contextmanager 503 @contextlib.contextmanager
496 def _TempFilename(name, contents=None): 504 def _TempFilename(name, contents=None):
497 """Create a temporary directory, append the specified name and yield. 505 """Create a temporary directory, append the specified name and yield.
498 506
499 In contrast to NamedTemporaryFile, does not keep the file open. 507 In contrast to NamedTemporaryFile, does not keep the file open.
500 Deletes the file on __exit__. 508 Deletes the file on __exit__.
501 """ 509 """
502 temp_dir = tempfile.mkdtemp(prefix=name) 510 temp_dir = tempfile.mkdtemp(prefix=name)
503 try: 511 try:
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
561 command = [exe, 'import', '-q', patch_dir, options.svn_repo, '--file', 569 command = [exe, 'import', '-q', patch_dir, options.svn_repo, '--file',
562 description_filename] 570 description_filename]
563 if scm.SVN.AssertVersion("1.5")[0]: 571 if scm.SVN.AssertVersion("1.5")[0]:
564 command.append('--no-ignore') 572 command.append('--no-ignore')
565 573
566 try: 574 try:
567 subprocess2.check_call(command) 575 subprocess2.check_call(command)
568 except subprocess2.CalledProcessError, e: 576 except subprocess2.CalledProcessError, e:
569 raise NoTryServerAccess(str(e)) 577 raise NoTryServerAccess(str(e))
570 578
579 PrintSuccess(bot_spec, options)
571 580
572 def _GetPatchGitRepo(git_url): 581 def _GetPatchGitRepo(git_url):
573 """Gets a path to a Git repo with patches. 582 """Gets a path to a Git repo with patches.
574 583
575 Stores patches in .git/git-try/patches-git directory, a git repo. If it 584 Stores patches in .git/git-try/patches-git directory, a git repo. If it
576 doesn't exist yet or its origin URL is different, cleans up and clones it. 585 doesn't exist yet or its origin URL is different, cleans up and clones it.
577 If it existed before, then pulls changes. 586 If it existed before, then pulls changes.
578 587
579 Does not support SVN repo. 588 Does not support SVN repo.
580 589
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
699 # Fetch, reset, update branch file again. 708 # Fetch, reset, update branch file again.
700 patch_git('fetch', 'origin') 709 patch_git('fetch', 'origin')
701 patch_git('reset', '--hard', 'origin/master') 710 patch_git('reset', '--hard', 'origin/master')
702 update_branch() 711 update_branch()
703 except subprocess2.CalledProcessError, e: 712 except subprocess2.CalledProcessError, e:
704 # Restore state. 713 # Restore state.
705 patch_git('checkout', 'master') 714 patch_git('checkout', 'master')
706 patch_git('reset', '--hard', 'origin/master') 715 patch_git('reset', '--hard', 'origin/master')
707 raise 716 raise
708 717
718 PrintSuccess(bot_spec, options)
719
720 def _SendChangeGerrit(bot_spec, options):
721 """Posts a try job to a Gerrit change.
722
723 Reads Change-Id from the HEAD commit, resolves the current revision, checks
724 that local revision matches the uploaded one, posts a try job in form of a
725 message, sets Tryjob label to 1.
726
727 Gerrit message format: starts with !tryjob, optionally followed by a tryjob
728 definition in JSON format:
729 buildNames: list of strings specifying build names.
730 """
731
732 logging.info('Sending by Gerrit')
733 if not options.gerrit_url:
734 raise NoTryServerAccess('Please use --gerrit_url option to specify the '
735 'Gerrit instance url to connect to')
736 gerrit_host = urlparse.urlparse(options.gerrit_url).hostname
737 logging.debug('Gerrit host: %s' % gerrit_host)
738
739 def GetChangeId(commmitish):
740 """Finds Change-ID of the HEAD commit."""
741 CHANGE_ID_RGX = '^Change-Id: (I[a-f0-9]{10,})'
742 comment = scm.GIT.Capture(['log', '-1', commmitish, '--format=%b'],
743 cwd=os.getcwd())
744 change_id_match = re.search(CHANGE_ID_RGX, comment, re.I | re.M)
745 if not change_id_match:
746 raise Error('Change-Id was not found in the HEAD commit. Make sure you '
747 'have a Git hook installed that generates and inserts a '
748 'Change-Id into a commit message automatically.')
749 change_id = change_id_match.group(1)
750 return change_id
751
752 def FormatMessage():
753 # Build job definition.
754 job_def = {}
755 builderNames = [builder for builder, _ in bot_spec]
756 if builderNames:
757 job_def['builderNames'] = builderNames
758
759 # Format message.
760 msg = '!tryjob'
761 if job_def:
762 msg = '%s %s' % (msg, json.dumps(job_def, sort_keys=True))
763 return msg
764
765 def PostTryjob(message):
766 logging.info('Posting gerrit message: %s' % message)
767 if not options.dry_run:
768 # Post a message and set TryJob=1 label.
769 try:
770 gerrit_util.SetReview(gerrit_host, change_id, msg=message,
771 labels={'Tryjob': 1})
772 except gerrit_util.GerritError, e:
773 if e.http_status == 400:
774 raise Error(e.reason)
775 else:
776 raise
777
778 head_sha = scm.GIT.Capture(['log', '-1', '--format=%H'], cwd=os.getcwd())
779
780 change_id = GetChangeId(head_sha)
781
782 # Check that the uploaded revision matches the local one.
783 changes = gerrit_util.GetChangeCurrentRevision(gerrit_host, change_id)
784 assert len(changes) <= 1, 'Multiple changes with id %s' % change_id
785 if not changes:
786 raise Error('A change %s was not found on the server. Was it uploaded?' %
787 change_id)
788 logging.debug('Found Gerrit change: %s' % changes[0])
789 if changes[0]['current_revision'] != head_sha:
790 raise Error('Please upload your latest local changes to Gerrit.')
791
792 # Post a try job.
793 message = FormatMessage()
794 PostTryjob(message)
795 change_url = urlparse.urljoin(options.gerrit_url,
796 '/#/c/%s' % changes[0]['_number'])
797 print('A tryjob was posted on change %s' % change_url)
709 798
710 def PrintSuccess(bot_spec, options): 799 def PrintSuccess(bot_spec, options):
711 if not options.dry_run: 800 if not options.dry_run:
712 text = 'Patch \'%s\' sent to try server' % options.name 801 text = 'Patch \'%s\' sent to try server' % options.name
713 if bot_spec: 802 if bot_spec:
714 text += ': %s' % ', '.join( 803 text += ': %s' % ', '.join(
715 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) 804 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec)
716 print(text) 805 print(text)
717 806
718 807
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
913 group.add_option("--use_git", 1002 group.add_option("--use_git",
914 action="store_const", 1003 action="store_const",
915 const=_SendChangeGit, 1004 const=_SendChangeGit,
916 dest="send_patch", 1005 dest="send_patch",
917 help="Use GIT to talk to the try server") 1006 help="Use GIT to talk to the try server")
918 group.add_option("-G", "--git_repo", 1007 group.add_option("-G", "--git_repo",
919 metavar="GIT_URL", 1008 metavar="GIT_URL",
920 help="GIT url to use to write the changes in; --use_git is " 1009 help="GIT url to use to write the changes in; --use_git is "
921 "implied when using --git_repo") 1010 "implied when using --git_repo")
922 parser.add_option_group(group) 1011 parser.add_option_group(group)
1012
1013 group = optparse.OptionGroup(parser, "Access the try server with Gerrit")
1014 group.add_option("--use_gerrit",
1015 action="store_const",
1016 const=_SendChangeGerrit,
1017 dest="send_patch",
1018 help="Use Gerrit to talk to the try server")
1019 group.add_option("--gerrit_url",
1020 metavar="GERRIT_URL",
1021 help="Gerrit url to post a tryjob to; --use_gerrit is "
1022 "implied when using --gerrit_url")
1023 parser.add_option_group(group)
1024
923 return parser 1025 return parser
924 1026
925 1027
926 def TryChange(argv, 1028 def TryChange(argv,
927 change, 1029 change,
928 swallow_exception, 1030 swallow_exception,
929 prog=None, 1031 prog=None,
930 extra_epilog=None): 1032 extra_epilog=None):
931 """ 1033 """
932 Args: 1034 Args:
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
1026 os.path.join(current_vcs.checkout_root, item), 1128 os.path.join(current_vcs.checkout_root, item),
1027 None) 1129 None)
1028 if checkout.checkout_root in [c.checkout_root for c in checkouts]: 1130 if checkout.checkout_root in [c.checkout_root for c in checkouts]:
1029 parser.error('Specified the root %s two times.' % 1131 parser.error('Specified the root %s two times.' %
1030 checkout.checkout_root) 1132 checkout.checkout_root)
1031 checkouts.append(checkout) 1133 checkouts.append(checkout)
1032 1134
1033 can_http = options.port and options.host 1135 can_http = options.port and options.host
1034 can_svn = options.svn_repo 1136 can_svn = options.svn_repo
1035 can_git = options.git_repo 1137 can_git = options.git_repo
1138 can_gerrit = options.gerrit_url
1139 can_something = can_http or can_svn or can_git or can_gerrit
1036 # If there was no transport selected yet, now we must have enough data to 1140 # If there was no transport selected yet, now we must have enough data to
1037 # select one. 1141 # select one.
1038 if not options.send_patch and not (can_http or can_svn or can_git): 1142 if not options.send_patch and not can_something:
1039 parser.error('Please specify an access method.') 1143 parser.error('Please specify an access method.')
1040 1144
1041 # Convert options.diff into the content of the diff. 1145 # Convert options.diff into the content of the diff.
1042 if options.url: 1146 if options.url:
1043 if options.files: 1147 if options.files:
1044 parser.error('You cannot specify files and --url at the same time.') 1148 parser.error('You cannot specify files and --url at the same time.')
1045 options.diff = urllib2.urlopen(options.url).read() 1149 options.diff = urllib2.urlopen(options.url).read()
1046 elif options.diff: 1150 elif options.diff:
1047 if options.files: 1151 if options.files:
1048 parser.error('You cannot specify files and --diff at the same time.') 1152 parser.error('You cannot specify files and --diff at the same time.')
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
1115 1219
1116 # Determine sending protocol 1220 # Determine sending protocol
1117 if options.send_patch: 1221 if options.send_patch:
1118 # If forced. 1222 # If forced.
1119 senders = [options.send_patch] 1223 senders = [options.send_patch]
1120 else: 1224 else:
1121 # Try sending patch using avaialble protocols 1225 # Try sending patch using avaialble protocols
1122 all_senders = [ 1226 all_senders = [
1123 (_SendChangeHTTP, can_http), 1227 (_SendChangeHTTP, can_http),
1124 (_SendChangeSVN, can_svn), 1228 (_SendChangeSVN, can_svn),
1125 (_SendChangeGit, can_git) 1229 (_SendChangeGerrit, can_gerrit),
1230 (_SendChangeGit, can_git),
1126 ] 1231 ]
1127 senders = [sender for sender, can in all_senders if can] 1232 senders = [sender for sender, can in all_senders if can]
1128 1233
1129 # Send the patch. 1234 # Send the patch.
1130 for sender in senders: 1235 for sender in senders:
1131 try: 1236 try:
1132 sender(bot_spec, options) 1237 sender(bot_spec, options)
1133 PrintSuccess(bot_spec, options)
1134 return 0 1238 return 0
1135 except NoTryServerAccess: 1239 except NoTryServerAccess:
1136 is_last = sender == senders[-1] 1240 is_last = sender == senders[-1]
1137 if is_last: 1241 if is_last:
1138 raise 1242 raise
1139 assert False, "Unreachable code" 1243 assert False, "Unreachable code"
1140 except (InvalidScript, NoTryServerAccess), e: 1244 except Error, e:
1141 if swallow_exception: 1245 if swallow_exception:
1142 return 1 1246 return 1
1143 print >> sys.stderr, e 1247 print >> sys.stderr, e
1144 return 1 1248 return 1
1145 except (gclient_utils.Error, subprocess2.CalledProcessError), e: 1249 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
1146 print >> sys.stderr, e 1250 print >> sys.stderr, e
1147 return 1 1251 return 1
1148 return 0 1252 return 0
1149 1253
1150 1254
1151 if __name__ == "__main__": 1255 if __name__ == "__main__":
1152 fix_encoding.fix_encoding() 1256 fix_encoding.fix_encoding()
1153 sys.exit(TryChange(None, None, False)) 1257 sys.exit(TryChange(None, None, False))
OLDNEW
« no previous file with comments | « tests/trychange_unittest.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698