1from __future__ import print_function, unicode_literals 2 3import logging 4 5import six 6 7from rbtools.api.errors import APIError 8from rbtools.commands import Command, CommandError, Option, OptionGroup 9from rbtools.utils.commands import stamp_commit_with_review_url 10from rbtools.utils.console import confirm 11from rbtools.utils.review_request import (find_review_request_by_change_id, 12 get_draft_or_current_value, 13 get_revisions, 14 guess_existing_review_request) 15 16 17class Stamp(Command): 18 """Add the review request URL to the commit message. 19 20 Stamps the review request URL onto the commit message of the revision 21 specified. The revisions argument behaves like it does in rbt post, where 22 it is required for some SCMs (e.g. Perforce) and unnecessary/ignored for 23 others (e.g. Git). 24 25 Normally, this command will guess the review request (based on the revision 26 number if provided, and the commit summary and description otherwise). 27 However, if a review request ID is specified by the user, it stamps the URL 28 of that review request instead of guessing. 29 """ 30 31 name = 'stamp' 32 author = 'The Review Board Project' 33 description = 'Adds the review request URL to the commit message.' 34 args = '[revisions]' 35 36 option_list = [ 37 OptionGroup( 38 name='Stamp Options', 39 description='Controls the behavior of a stamp, including what ' 40 'review request URL gets stamped.', 41 option_list=[ 42 Option('-r', '--review-request-id', 43 dest='rid', 44 metavar='ID', 45 default=None, 46 help='Specifies the existing review request ID to ' 47 'be stamped.'), 48 ] 49 ), 50 Command.server_options, 51 Command.repository_options, 52 Command.diff_options, 53 Command.branch_options, 54 Command.perforce_options, 55 ] 56 57 def no_commit_error(self): 58 raise CommandError('No existing commit to stamp on.') 59 60 def _ask_review_request_match(self, review_request): 61 question = ('Stamp with Review Request #%s: "%s"? ' 62 % (review_request.id, 63 get_draft_or_current_value( 64 'summary', review_request))) 65 66 return confirm(question) 67 68 def determine_review_request(self, api_client, api_root, repository_info, 69 repository_name, revisions): 70 """Determine the correct review request for a commit. 71 72 A tuple (review request ID, review request absolute URL) is returned. 73 If no review request ID is found by any of the strategies, 74 (None, None) is returned. 75 """ 76 # First, try to match the changeset to a review request directly. 77 if repository_info.supports_changesets: 78 review_request = find_review_request_by_change_id( 79 api_client, api_root, repository_info, repository_name, 80 revisions) 81 82 if review_request and review_request.id: 83 return review_request.id, review_request.absolute_url 84 85 # Fall back on guessing based on the description. This may return None 86 # if no suitable review request is found. 87 logging.debug('Attempting to guess review request based on ' 88 'summary and description') 89 90 try: 91 review_request = guess_existing_review_request( 92 repository_info, repository_name, api_root, api_client, 93 self.tool, revisions, guess_summary=False, 94 guess_description=False, 95 is_fuzzy_match_func=self._ask_review_request_match, 96 no_commit_error=self.no_commit_error) 97 except ValueError as e: 98 raise CommandError(six.text_type(e)) 99 100 if review_request: 101 logging.debug('Found review request ID %d', review_request.id) 102 return review_request.id, review_request.absolute_url 103 else: 104 logging.debug('Could not find a matching review request') 105 return None, None 106 107 def main(self, *args): 108 """Add the review request URL to a commit message.""" 109 self.cmd_args = list(args) 110 111 repository_info, self.tool = self.initialize_scm_tool( 112 client_name=self.options.repository_type) 113 server_url = self.get_server_url(repository_info, self.tool) 114 api_client, api_root = self.get_api(server_url) 115 self.setup_tool(self.tool, api_root=api_root) 116 117 if not self.tool.can_amend_commit: 118 raise NotImplementedError('rbt stamp is not supported with %s.' 119 % self.tool.name) 120 121 try: 122 if self.tool.has_pending_changes(): 123 raise CommandError('Working directory is not clean.') 124 except NotImplementedError: 125 pass 126 127 revisions = get_revisions(self.tool, self.cmd_args) 128 129 # Use the ID from the command line options if present. 130 if self.options.rid: 131 review_request_id = self.options.rid 132 133 try: 134 review_request = api_root.get_review_request( 135 review_request_id=review_request_id) 136 except APIError as e: 137 raise CommandError('Error getting review request %s: %s' 138 % (review_request_id, e)) 139 140 review_request_url = review_request.absolute_url 141 else: 142 review_request_id, review_request_url = \ 143 self. determine_review_request( 144 api_client, api_root, repository_info, 145 self.options.repository_name, revisions) 146 147 if not review_request_url: 148 raise CommandError('Could not determine the existing review ' 149 'request URL to stamp with.') 150 151 stamp_commit_with_review_url(revisions, review_request_url, self.tool) 152 153 print('Successfully stamped change with the URL:') 154 print(review_request_url) 155