def prompt_rb_repository(self, tool_name, repository_info, api_root): """Interactively prompt to select a matching repository. The user is prompted to choose a matching repository found on the Review Board server. """ # Go through each matching repo and prompt for a selection. If a # selection is made, immediately return the selected repo. for repository_page in api_root.get_repositories().all_pages: repo_paths = {} for repository in repository_page: if repository.tool != tool_name: continue repo_paths[repository['path']] = repository if 'mirror_path' in repository: repo_paths[repository['mirror_path']] = repository closest_path = difflib.get_close_matches(repository_info.path, six.iterkeys(repo_paths), n=1) for path in closest_path: repo = repo_paths[path] question = ('Use the %s repository "%s" (%s)?' % (tool_name, repo['name'], repo['path'])) if confirm(question): return repo return None
def _ask_review_request_match(self, review_request): question = ('Stamp with Review Request #%s: "%s"? ' % (review_request.id, get_draft_or_current_value( 'summary', review_request))) return confirm(question)
def prompt_rb_repository(self, tool_name, repository_info, api_root): """Interactively prompt to select a matching repository. The user is prompted to choose a matching repository found on the Review Board server. """ repositories = api_root.get_repositories() # Go through each matching repo and prompt for a selection. If a # selection is made, immediately return the selected repo. try: while True: repo_paths = {} for repository in repositories: if repository.tool != tool_name: continue repo_paths[repository["path"]] = repository if "mirror_path" in repository: repo_paths[repository["mirror_path"]] = repository closest_path = difflib.get_close_matches(repository_info.path, repo_paths.keys(), n=1) for path in closest_path: repo = repo_paths[path] question = "Use the %s repository '%s' (%s)?" % (tool_name, repo["name"], repo["path"]) if confirm(question): return repo repositories = repositories.get_next() except StopIteration: pass return None
def _ask_review_request_match(self, review_request): question = ("Update Review Request #%s: '%s'? " % (review_request.id, get_draft_or_current_value( 'summary', review_request))) return confirm(question)
def prompt_rb_repository(self, tool_name, repository_info, api_root): """Interactively prompt to select a matching repository. The user is prompted to choose a matching repository found on the Review Board server. """ repositories = api_root.get_repositories() # Go through each matching repo and prompt for a selection. If a # selection is made, immediately return the selected repo. try: while True: for repo in repositories: is_match = ( tool_name == repo.tool and repository_info.path in (repo['path'], getattr(repo, 'mirror_path', ''))) if is_match: question = ( "Use the %s repository '%s' (%s)?" % (tool_name, repo['name'], repo['path'])) if confirm(question): return repo repositories = repositories.get_next() except StopIteration: pass return None
def prompt_rb_repository(self, tool_name, repository_info, api_root): """Interactively prompt to select a matching repository. The user is prompted to choose a matching repository found on the Review Board server. """ repositories = api_root.get_repositories() # Go through each matching repo and prompt for a selection. If a # selection is made, immediately return the selected repo. try: while True: for repo in repositories: is_match = (tool_name == repo.tool and repository_info.path in (repo['path'], getattr(repo, 'mirror_path', ''))) if is_match: question = ("Use the %s repository '%s' (%s)?" % (tool_name, repo['name'], repo['path'])) if confirm(question): return repo repositories = repositories.get_next() except StopIteration: pass return None
def main(self, *args): server = self.options.server if not server: server = input('Enter the Review Board server URL: ') repository_info, tool = self.initialize_scm_tool() api_client, api_root = self.get_api(server) self.setup_tool(tool, api_root=api_root) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) selected_repo = self.prompt_rb_repository( tool.name, repository_info, api_root) if not selected_repo: print('No %s repository found or selected for %s. %s not created.' % (tool.name, server, CONFIG_FILE)) return config = [ ('REVIEWBOARD_URL', server), ('REPOSITORY', selected_repo['name']), ('REPOSITORY_TYPE', tool.entrypoint_name), ] try: branch = tool.get_current_branch() config.append(('BRANCH', branch)) config.append(('LAND_DEST_BRANCH', branch)) except NotImplementedError: pass outfile_path = os.path.join(os.getcwd(), CONFIG_FILE) output = self._get_output(config) if not os.path.exists(outfile_path): question = ('Create "%s" with the following?\n\n%s\n' % (outfile_path, output)) else: question = ('"%s" exists. Overwrite with the following?\n\n%s\n' % (outfile_path, output)) if not confirm(question): return self.generate_config_file(outfile_path, config)
def main(self, *args): server = self.options.server if not server: server = raw_input('Enter the Review Board server URL: ') repository_info, tool = self.initialize_scm_tool() api_client, api_root = self.get_api(server) self.setup_tool(tool, api_root=api_root) selected_repo = self.prompt_rb_repository( tool.name, repository_info, api_root) if not selected_repo: print ("No %s repository found or selected for %s. %s not created." % (tool.name, server, CONFIG_FILE)) return config = [ ('REVIEWBOARD_URL', server), ('REPOSITORY', selected_repo['name']) ] try: branch = tool.get_current_branch() config.append(('BRANCH', branch)) except NotImplementedError: pass outfile_path = os.path.join(os.getcwd(), CONFIG_FILE) output = self._get_output(config) if not os.path.exists(outfile_path): question = ("Create '%s' with the following?\n\n%s\n" % (outfile_path, output)) else: question = ("'%s' exists. Overwrite with the following?\n\n%s\n" % (outfile_path, output)) if not confirm(question): return self.generate_config_file(outfile_path, config)
def main(self, *args): server = self.options.server if not server: server = raw_input('Enter the Review Board server URL: ') repository_info, tool = self.initialize_scm_tool() api_client, api_root = self.get_api(server) self.setup_tool(tool, api_root=api_root) selected_repo = self.prompt_rb_repository(tool.name, repository_info, api_root) if not selected_repo: print( "No %s repository found or selected for %s. %s not created." % (tool.name, server, CONFIG_FILE)) return config = [('REVIEWBOARD_URL', server), ('REPOSITORY', selected_repo['name'])] try: branch = tool.get_current_branch() config.append(('BRANCH', branch)) except NotImplementedError: pass outfile_path = os.path.join(os.getcwd(), CONFIG_FILE) output = self._get_output(config) if not os.path.exists(outfile_path): question = ("Create '%s' with the following?\n\n%s\n" % (outfile_path, output)) else: question = ("'%s' exists. Overwrite with the following?\n\n%s\n" % (outfile_path, output)) if not confirm(question): return self.generate_config_file(outfile_path, config)
def prompt_rb_repository(self, tool_name, repository_info, api_root): """Interactively prompt to select a matching repository. The user is prompted to choose a matching repository found on the Review Board server. """ repositories = api_root.get_repositories() # Go through each matching repo and prompt for a selection. If a # selection is made, immediately return the selected repo. try: while True: repo_paths = {} for repository in repositories: if repository.tool != tool_name: continue repo_paths[repository['path']] = repository if 'mirror_path' in repository: repo_paths[repository['mirror_path']] = repository closest_path = difflib.get_close_matches(repository_info.path, repo_paths.keys(), n=1) for path in closest_path: repo = repo_paths[path] question = ( "Use the %s repository '%s' (%s)?" % (tool_name, repo['name'], repo['path'])) if confirm(question): return repo repositories = repositories.get_next() except StopIteration: pass return None
def main(self, *args): server = self.options.server api_client = None api_root = None if not server: print() print(textwrap.fill( 'This command is intended to help users create a %s file in ' 'the current directory to connect a repository and Review ' 'Board server.') % CONFIG_FILE) print() print(textwrap.fill( 'Repositories must currently exist on your server (either ' 'hosted internally or via RBCommons) to successfully ' 'generate this file.')) print(textwrap.fill( 'Repositories can be added using the Admin Dashboard in ' 'Review Board or under your team administration settings in ' 'RBCommons.')) print() print(textwrap.fill( 'Press CTRL + C anytime during this command to cancel ' 'generating your config file.')) print() while True: server = input('Enter the Review Board server URL: ') if server: try: # Validate the inputted server. api_client, api_root = self.get_api(server) break except CommandError as e: print() print('%s' % e) print('Please try again.') print() repository_info, tool = self.initialize_scm_tool() # If we run the `--server` option or if we run `rbt setup-repo` with # a server URL already defined in our config file, neither `api_root` # nor `api_client` will be defined. We must re-validate our server # again. if not api_root and not api_client: api_client, api_root = self.get_api(server) self.setup_tool(tool, api_root=api_root) # Check if repository info on reviewboard server match local ones. repository_info = repository_info.find_server_repository_info(api_root) # While a repository is not chosen, keep the repository selection # prompt displayed until the prompt is cancelled. while True: print() print('Current server: %s' % server) selected_repo = self.prompt_rb_repository( tool.name, repository_info, api_root) if not selected_repo: print() print('No %s repository found for the Review Board server %s' % (tool.name, server)) print() print('Cancelling %s creation...' % CONFIG_FILE) print() print(textwrap.fill( 'Please make sure your repositories currently exist on ' 'your server. Repositories can be configured using the ' 'Review Board Admin Dashboard or under your team ' 'administration settings in RBCommons. For more ' 'information, see `rbt help setup-repo` or the official ' 'docs at https://www.reviewboard.org/docs/.')) return config = [ ('REVIEWBOARD_URL', server), ('REPOSITORY', selected_repo['name']), ('REPOSITORY_TYPE', tool.entrypoint_name), ] try: branch = tool.get_current_branch() config.append(('BRANCH', branch)) config.append(('LAND_DEST_BRANCH', branch)) except NotImplementedError: pass outfile_path = os.path.join(os.getcwd(), CONFIG_FILE) output = self._get_output(config) if not os.path.exists(outfile_path): question = ('Create "%s" with the following?\n\n%s\n' % (outfile_path, output)) else: question = ( '"%s" exists. Overwrite with the following?\n\n%s\n' % (outfile_path, output) ) if confirm(question): break self.generate_config_file(outfile_path, config)
def _ask_review_request_match(self, review_request): return confirm( 'Land Review Request #%s: "%s"? ' % (review_request.id, get_draft_or_current_value('summary', review_request)))
class Post(Command): """Create and update review requests.""" name = "post" author = "The Review Board Project" description = "Uploads diffs to create and update review requests." args = "[revisions]" GUESS_AUTO = 'auto' GUESS_YES = 'yes' GUESS_NO = 'no' GUESS_YES_INPUT_VALUES = (True, 'yes', 1, '1') GUESS_NO_INPUT_VALUES = (False, 'no', 0, '0') GUESS_CHOICES = (GUESS_AUTO, GUESS_YES, GUESS_NO) option_list = [ OptionGroup( name='Posting Options', description='Controls the behavior of a post, including what ' 'review request gets posted and how, and what ' 'happens after it is posted.', option_list=[ Option('-r', '--review-request-id', dest='rid', metavar='ID', default=None, help='Specifies the existing review request ID to ' 'update.'), Option('-u', '--update', dest='update', action='store_true', default=False, help='Automatically determines the existing review ' 'request to update.'), Option('-p', '--publish', dest='publish', action='store_true', default=False, config_key='PUBLISH', help='Immediately publishes the review request after ' 'posting.'), Option('-o', '--open', dest='open_browser', action='store_true', config_key='OPEN_BROWSER', default=False, help='Opens a web browser to the review request ' 'after posting.'), Option('--submit-as', dest='submit_as', metavar='USERNAME', config_key='SUBMIT_AS', default=None, help='The user name to use as the author of the ' 'review request, instead of the logged in user.'), Option('--change-only', dest='change_only', action='store_true', default=False, help='Updates fields from the change description, ' 'but does not upload a new diff ' '(Perforce/Plastic only).'), Option('--diff-only', dest='diff_only', action='store_true', default=False, help='Uploads a new diff, but does not update ' 'fields from the change description ' '(Perforce/Plastic only).'), ] ), Command.server_options, Command.repository_options, OptionGroup( name='Review Request Field Options', description='Options for setting the contents of fields in the ' 'review request.', option_list=[ Option('-g', '--guess-fields', dest='guess_fields', action='store', config_key='GUESS_FIELDS', nargs='?', default=GUESS_AUTO, const=GUESS_YES, choices=GUESS_CHOICES, help='Short-hand for --guess-summary ' '--guess-description.'), Option('--guess-summary', dest='guess_summary', action='store', config_key='GUESS_SUMMARY', nargs='?', default=GUESS_AUTO, const=GUESS_YES, choices=GUESS_CHOICES, help='Generates the Summary field based on the ' 'commit messages (Bazaar/Git/Mercurial only).'), Option('--guess-description', dest='guess_description', action='store', config_key='GUESS_DESCRIPTION', nargs='?', default=GUESS_AUTO, const=GUESS_YES, choices=GUESS_CHOICES, help='Generates the Description field based on the ' 'commit messages (Bazaar/Git/Mercurial only).'), Option('--change-description', default=None, help='A description of what changed in this update ' 'of the review request. This is ignored for new ' 'review requests.'), Option('--summary', dest='summary', default=None, help='The new contents for the Summary field.'), Option('--description', dest='description', default=None, help='The new contents for the Description field.'), Option('--description-file', dest='description_file', default=None, metavar='FILENAME', help='A text file containing the new contents for the ' 'Description field.'), Option('--testing-done', dest='testing_done', default=None, help='The new contents for the Testing Done field.'), Option('--testing-done-file', dest='testing_file', default=None, metavar='FILENAME', help='A text file containing the new contents for the ' 'Testing Done field.'), Option('--branch', dest='branch', config_key='BRANCH', default=None, help='The branch the change will be committed on.'), Option('--bugs-closed', dest='bugs_closed', default=None, help='The comma-separated list of bug IDs closed.'), Option('--target-groups', dest='target_groups', config_key='TARGET_GROUPS', default=None, help='The names of the groups that should perform the ' 'review.'), Option('--target-people', dest='target_people', config_key='TARGET_PEOPLE', default=None, help='The usernames of the people who should perform ' 'the review.'), Option('--depends-on', dest='depends_on', config_key='DEPENDS_ON', default=None, help='The new contents for the Depends On field.'), Option('--markdown', dest='markdown', action='store_true', config_key='MARKDOWN', default=False, help='Specifies if the summary and description should ' 'be interpreted as Markdown-formatted text ' '(Review Board 2.0+ only).'), ] ), Command.diff_options, Command.perforce_options, Command.subversion_options, ] def post_process_options(self): # -g implies --guess-summary and --guess-description if self.options.guess_fields: self.options.guess_fields = self.normalize_guess_value( self.options.guess_fields, '--guess-fields') self.options.guess_summary = self.options.guess_fields self.options.guess_description = self.options.guess_fields if self.options.revision_range: raise CommandError( 'The --revision-range argument has been removed. To post a ' 'diff for one or more specific revisions, pass those ' 'revisions as arguments. For more information, see the ' 'RBTools 0.6 Release Notes.') if self.options.svn_changelist: raise CommandError( 'The --svn-changelist argument has been removed. To use a ' 'Subversion changelist, pass the changelist name as an ' 'additional argument after the command.') # Only one of --description and --description-file can be used if self.options.description and self.options.description_file: raise CommandError("The --description and --description-file " "options are mutually exclusive.\n") # If --description-file is used, read that file if self.options.description_file: if os.path.exists(self.options.description_file): fp = open(self.options.description_file, "r") self.options.description = fp.read() fp.close() else: raise CommandError( "The description file %s does not exist.\n" % self.options.description_file) # Only one of --testing-done and --testing-done-file can be used if self.options.testing_done and self.options.testing_file: raise CommandError("The --testing-done and --testing-done-file " "options are mutually exclusive.\n") # If --testing-done-file is used, read that file if self.options.testing_file: if os.path.exists(self.options.testing_file): fp = open(self.options.testing_file, "r") self.options.testing_done = fp.read() fp.close() else: raise CommandError("The testing file %s does not exist.\n" % self.options.testing_file) # If we have an explicitly specified summary, override # --guess-summary if self.options.summary: self.options.guess_summary = self.GUESS_NO else: self.options.guess_summary = self.normalize_guess_value( self.options.guess_summary, '--guess-summary') # If we have an explicitly specified description, override # --guess-description if self.options.description: self.options.guess_description = self.GUESS_NO else: self.options.guess_description = self.normalize_guess_value( self.options.guess_description, '--guess-description') # If we have an explicitly specified review request ID, override # --update if self.options.rid and self.options.update: self.options.update = False def normalize_guess_value(self, guess, arg_name): if guess in self.GUESS_YES_INPUT_VALUES: return self.GUESS_YES elif guess in self.GUESS_NO_INPUT_VALUES: return self.GUESS_NO elif guess == self.GUESS_AUTO: return guess else: raise CommandError('Invalid value "%s" for argument "%s"' % (guess, arg_name)) def get_repository_path(self, repository_info, api_root): """Get the repository path from the server. This will compare the paths returned by the SCM client with those one the server, and return the first match. """ if isinstance(repository_info.path, list): repositories = api_root.get_repositories() try: while True: for repo in repositories: if repo['path'] in repository_info.path: repository_info.path = repo['path'] raise StopIteration() repositories = repositories.get_next() except StopIteration: pass if isinstance(repository_info.path, list): error_str = [ 'There was an error creating this review request.\n', '\n', 'There was no matching repository path found on the server.\n', 'Unknown repository paths found:\n', ] for foundpath in repository_info.path: error_str.append('\t%s\n' % foundpath) error_str += [ 'Ask the administrator to add one of these repositories\n', 'to the Review Board server.\n', ] raise CommandError(''.join(error_str)) return repository_info.path def get_draft_or_current_value(self, field_name, review_request): """Returns the draft or current field value from a review request. If a draft exists for the supplied review request, return the draft's field value for the supplied field name, otherwise return the review request's field value for the supplied field name. """ if review_request.draft: fields = review_request.draft[0] else: fields = review_request return fields[field_name] def get_possible_matches(self, review_requests, summary, description, limit=5): """Returns a sorted list of tuples of score and review request. Each review request is given a score based on the summary and description provided. The result is a sorted list of tuples containing the score and the corresponding review request, sorted by the highest scoring review request first. """ candidates = [] # Get all potential matches. try: while True: for review_request in review_requests: summary_pair = ( self.get_draft_or_current_value( 'summary', review_request), summary) description_pair = ( self.get_draft_or_current_value( 'description', review_request), description) score = Score.get_match(summary_pair, description_pair) candidates.append((score, review_request)) review_requests = review_requests.get_next() except StopIteration: pass # Sort by summary and description on descending rank. sorted_candidates = sorted( candidates, key=lambda m: (m[0].summary_score, m[0].description_score), reverse=True ) return sorted_candidates[:limit] def num_exact_matches(self, possible_matches): """Returns the number of exact matches in the possible match list.""" count = 0 for score, request in possible_matches: if score.is_exact_match(): count += 1 return count def guess_existing_review_request_id(self, repository_info, api_root, api_client): """Try to guess the existing review request ID if it is available. The existing review request is guessed by comparing the existing summary and description to the current post's summary and description, respectively. The current post's summary and description are guessed if they are not provided. If the summary and description exactly match those of an existing review request, the ID for which is immediately returned. Otherwise, the user is prompted to select from a list of potential matches, sorted by the highest ranked match first. """ user = get_user(api_client, api_root, auth_required=True) repository_id = get_repository_id( repository_info, api_root, self.options.repository_name) try: # Get only pending requests by the current user for this # repository. review_requests = api_root.get_review_requests( repository=repository_id, from_user=user.username, status='pending', expand='draft') if not review_requests: raise CommandError('No existing review requests to update for ' 'user %s.' % user.username) except APIError, e: raise CommandError('Error getting review requests for user ' '%s: %s' % (user.username, e)) summary = self.options.summary description = self.options.description if not summary or not description: try: commit_message = self.get_commit_message() if commit_message: if not summary: summary = commit_message['summary'] if not description: description = commit_message['description'] except NotImplementedError: raise CommandError('--summary and --description are required.') possible_matches = self.get_possible_matches(review_requests, summary, description) exact_match_count = self.num_exact_matches(possible_matches) for score, review_request in possible_matches: # If the score is the only exact match, return the review request # ID without confirmation, otherwise prompt. if score.is_exact_match() and exact_match_count == 1: return review_request.id else: question = ("Update Review Request #%s: '%s'? " % (review_request.id, self.get_draft_or_current_value( 'summary', review_request))) if confirm(question): return review_request.id return None
def main(self, *args): server = self.options.server api_client = None api_root = None self.stdout.new_line() self.stdout.write( textwrap.fill( 'This command is intended to help users create a %s file in ' 'the current directory to connect a repository and Review ' 'Board server.') % CONFIG_FILE) self.stdout.new_line() self.stdout.write( textwrap.fill( 'Repositories must currently exist on your server (either ' 'hosted internally or via RBCommons) to successfully ' 'generate this file.')) self.stdout.write( textwrap.fill( 'Repositories can be added using the Admin Dashboard in ' 'Review Board or under your team administration settings in ' 'RBCommons.')) self.stdout.new_line() self.stdout.write( textwrap.fill( 'Press CTRL + C anytime during this command to cancel ' 'generating your config file.')) self.stdout.new_line() while True: if server: try: # Validate the inputted server. api_client, api_root = self.get_api(server) break except CommandError as e: self.stdout.new_line() self.stdout.write('%s' % e) self.stdout.write('Please try again.') self.stdout.new_line() server = input('Enter the Review Board server URL: ') repository_info, tool = self.initialize_scm_tool() self.capabilities = self.get_capabilities(api_root) tool.capabilities = self.capabilities # Go through standard detection mechanism first. If we find a match # this way, we'll set the local repository_info path to be the same as # the remote, which will improve matching. repository, info = get_repository_resource( api_root, tool=tool, repository_paths=repository_info.path) if repository: repository_info.update_from_remote(repository, info) # While a repository is not chosen, keep the repository selection # prompt displayed until the prompt is cancelled. while True: self.stdout.new_line() self.stdout.write('Current server: %s' % server) selected_repo = self.prompt_rb_repository( local_tool_name=tool.name, server_tool_names=tool.server_tool_names, repository_paths=repository_info.path, api_root=api_root) if not selected_repo: self.stdout.new_line() self.stdout.write('No %s repository found for the Review ' 'Board server %s' % (tool.name, server)) self.stdout.new_line() self.stdout.write('Cancelling %s creation...' % CONFIG_FILE) self.stdout.new_line() self.stdout.write( textwrap.fill('Please make sure your repositories ' 'currently exist on your server. ' 'Repositories can be configured using the ' 'Review Board Admin Dashboard or under your ' 'team administration settings in RBCommons. ' 'For more information, see `rbt help ' 'setup-repo` or the official docs at ' 'https://www.reviewboard.org/docs/.')) return config = [ ('REVIEWBOARD_URL', server), ('REPOSITORY', selected_repo['name']), ('REPOSITORY_TYPE', tool.entrypoint_name), ] try: branch = tool.get_current_branch() config.append(('BRANCH', branch)) config.append(('LAND_DEST_BRANCH', branch)) except NotImplementedError: pass outfile_path = os.path.join(os.getcwd(), CONFIG_FILE) output = self._get_output(config) if not os.path.exists(outfile_path): question = ('Create "%s" with the following?\n\n%s\n' % (outfile_path, output)) else: question = ( '"%s" exists. Overwrite with the following?\n\n%s\n' % (outfile_path, output)) if confirm(question): break self.generate_config_file(outfile_path, config)