def diff(self, revisions, include_files, exclude_patterns): """Return the generated diff. Args: revisions (dict): A dictionary containing ``base`` and ``tip`` keys. include_files (list): A list of file paths to include in the diff. exclude_patterns (list): A list of file paths to exclude from the diff. Returns: dict: A dictionary containing ``diff``, ``parent_diff``, and ``base_commit_id`` keys. In the case of TFS, the parent diff key will always be ``None``. """ base = str(revisions['base']) tip = str(revisions['tip']) if tip == self.REVISION_WORKING_COPY: return self._diff_working_copy(base, include_files, exclude_patterns) else: die('Posting committed changes is not yet supported for TFS.')
def get_repository_info(self): if not self.p4.is_supported(): return None p4_info = self.p4.info() # For the repository path, we first prefer p4 brokers, then the # upstream p4 server. If neither of those are found, just return None. repository_path = p4_info.get('Broker address', None) if repository_path is None: repository_path = p4_info.get('Server address', None) if repository_path is None: return None try: parts = repository_path.split(':') hostname = None if len(parts) == 3 and parts[0] == 'ssl': hostname = parts[1] port = parts[2] elif len(parts) == 2: hostname, port = parts if not hostname: die('Path %s is not a valid Perforce P4PORT' % repository_path) info = socket.gethostbyaddr(hostname) # If aliases exist for hostname, create a list of alias:port # strings for repository_path. if info[1]: servers = [info[0]] + info[1] repository_path = ["%s:%s" % (server, port) for server in servers] else: repository_path = "%s:%s" % (info[0], port) except (socket.gaierror, socket.herror): pass server_version = p4_info.get('Server version', None) if not server_version: return None m = re.search(r'[^ ]*/([0-9]+)\.([0-9]+)/[0-9]+ .*$', server_version, re.M) if m: self.p4d_version = int(m.group(1)), int(m.group(2)) else: # Gracefully bail if we don't get a match return None # Now that we know it's Perforce, make sure we have GNU diff # installed, and error out if we don't. check_gnu_diff() return RepositoryInfo(path=repository_path, supports_changesets=True)
def credentials_prompt(self, realm, uri, username=None, password=None, *args, **kwargs): """Prompt the user for credentials using the command line. This will prompt the user, and then return the provided username and password. This is used as a callback in the API when the user requires authorization. """ if username is None or password is None: if getattr(self.options, 'diff_filename', None) == '-': die('HTTP authentication is required, but cannot be ' 'used with --diff-filename=-') print() print('Please log in to the Review Board server at %s.' % urlparse(uri)[1]) # getpass will write its prompt to stderr but input # writes to stdout. See bug 2831. if username is None: sys.stderr.write('Username: '******'Password: ') return username, password
def _run_p4(self, command): """Execute a perforce command using the python marshal API. - command: A list of strings of the command to execute. The return type depends on the command being run. """ command = ['p4', '-G'] + command p = subprocess.Popen(command, stdout=subprocess.PIPE) result = [] has_error = False while 1: try: data = marshal.load(p.stdout) except EOFError: break else: result.append(data) if data.get('code', None) == 'error': has_error = True rc = p.wait() if rc or has_error: for record in result: if 'data' in record: print record['data'] die('Failed to execute command: %s\n' % (command,)) return result
def get_repository_info(self): if not check_install('cm version'): return None # Get the repository that the current directory is from. If there # is more than one repository mounted in the current directory, # bail out for now (in future, should probably enter a review # request per each repository.) split = execute(["cm", "ls", "--format={8}"], split_lines=True, ignore_errors=True) m = re.search(r'^rep:(.+)$', split[0], re.M) if not m: return None # Make sure the repository list contains only one unique entry if len(split) != split.count(split[0]): # Not unique! die('Directory contains more than one mounted repository') path = m.group(1) # Get the workspace directory, so we can strip it from the diff output self.workspacedir = execute(["cm", "gwp", ".", "--format={1}"], split_lines=False, ignore_errors=True).strip() logging.debug("Workspace is %s" % self.workspacedir) return RepositoryInfo(path, supports_changesets=True, supports_parent_diffs=False)
def main(self, *args): """Create and update review requests.""" self.post_process_options() origcwd = os.path.abspath(os.getcwd()) repository_info, tool = self.initialize_scm_tool() server_url = self.get_server_url(repository_info, tool) api_root = self.get_root(server_url) self.setup_tool(tool, api_root=api_root) if self.options.revision_range: diff, parent_diff = tool.diff_between_revisions( self.options.revision_range, args, repository_info) elif self.options.svn_changelist: diff, parent_diff = tool.diff_changelist( self.options.svn_changelist) elif self.options.diff_filename: parent_diff = None if self.options.diff_filename == '-': diff = sys.stdin.read() else: try: diff_path = os.path.join(origcwd, self.options.diff_filename) fp = open(diff_path, 'r') diff = fp.read() fp.close() except IOError, e: die("Unable to open diff filename: %s" % e)
def otp_token_prompt(self, uri, token_method, *args, **kwargs): """Prompt the user for a one-time password token. Their account is configured with two-factor authentication. The server will have sent a token to their configured mobile device or application. The user will be prompted for this token. """ if getattr(self.options, "diff_filename", None) == "-": die("A two-factor authentication token is required, but cannot " "be used with --diff-filename=-") print print "==> Two-factor authentication token required" if token_method == "sms": print ("You should be getting a text message with " "an authentication token.") print "Enter the token below." elif token_method == "call": print ("You should be getting an automated phone call with " "an authentication token.") print "Enter the token below." elif token_method == "generator": print "Enter the token shown on your token generator app below." print return getpass.getpass("Token: ")
def post_request(self, tool, repository_info, server_url, api_root, changenum=None, diff_content=None, parent_diff_content=None, submit_as=None, retries=3): """Creates or updates a review request, and uploads a diff. On success the review request id and url are returned. """ if self.options.rid: # Retrieve the review request coresponding to the user # provided id. try: review_request = api_root.get_review_request( review_request_id=self.options.rid) except APIError, e: die("Error getting review request %s: %s" % (self.options.rid, e)) if review_request.status == 'submitted': die("Review request %s is marked as %s. In order to " "update it, please reopen the request and try again." % (self.options.rid, review_request.status))
def otp_token_prompt(self, uri, token_method, *args, **kwargs): """Prompt the user for a one-time password token. Their account is configured with two-factor authentication. The server will have sent a token to their configured mobile device or application. The user will be prompted for this token. """ if getattr(self.options, 'diff_filename', None) == '-': die('A two-factor authentication token is required, but cannot ' 'be used with --diff-filename=-') print() print('Please enter your two-factor authentication token for Review ' 'Board.') if token_method == 'sms': print('You should be getting a text message with ' 'an authentication token.') print('Enter the token below.') elif token_method == 'call': print('You should be getting an automated phone call with ' 'an authentication token.') print('Enter the token below.') elif token_method == 'generator': print('Enter the token shown on your token generator app below.') print() return getpass.getpass(b'Token: ')
def _die(self, msg, e): """Remove the connection to the database and print an error message.""" self.db = None # So that _write_db doesn't cause an exception logging.error('%s: %s. Try running "rbt clear-cache" to manually ' 'clear the HTTP cache for the API.', msg, e) die()
def check_gnu_diff(): """Checks if GNU diff is installed, and informs the user if it's not.""" has_gnu_diff = False try: if hasattr(os, 'uname') and os.uname()[0] == 'SunOS': diff_cmd = 'gdiff' else: diff_cmd = 'diff' result = execute([diff_cmd, '--version'], ignore_errors=True) has_gnu_diff = 'GNU diffutils' in result except OSError: pass if not has_gnu_diff: sys.stderr.write('\n') sys.stderr.write('GNU diff is required in order to generate diffs. ' 'Make sure it is installed\n') sys.stderr.write('and in the path.\n') sys.stderr.write('\n') if os.name == 'nt': sys.stderr.write('On Windows, you can install this from:\n') sys.stderr.write(GNU_DIFF_WIN32_URL) sys.stderr.write('\n') die()
def _run_p4(self, command): """Execute a perforce command using the python marshal API. - command: A list of strings of the command to execute. The return type depends on the command being run. """ command = ['p4', '-G'] + command p = subprocess.Popen(command, stdout=subprocess.PIPE) result = [] has_error = False while 1: try: data = marshal.load(p.stdout) except EOFError: break else: result.append(data) if data.get('code', None) == 'error': has_error = True rc = p.wait() if rc or has_error: for record in result: if 'data' in record: print record['data'] die('Failed to execute command: %s\n' % (command, )) return result
def main(): origcwd = os.path.abspath(os.getcwd()) if 'APPDATA' in os.environ: homepath = os.environ['APPDATA'] elif 'HOME' in os.environ: homepath = os.environ["HOME"] else: homepath = '' # If we end up creating a cookie file, make sure it's only readable by the # user. os.umask(0077) # Load the config and cookie files cookie_file = os.path.join(homepath, ".post-review-cookies.txt") user_config, globals()['configs'] = load_config_files(homepath) args = parse_options(sys.argv[1:]) debug('RBTools %s' % get_version_string()) debug('Python %s' % sys.version) debug('Running on %s' % (platform.platform())) debug('Home = %s' % homepath) debug('Current Directory = %s' % os.getcwd()) debug( 'Checking the repository type. Errors shown below are mostly harmless.' ) repository_info, tool = scan_usable_client(options) debug('Finished checking the repository type.') tool.user_config = user_config tool.configs = configs # Verify that options specific to an SCM Client have not been mis-used. tool.check_options() if repository_info.supports_changesets: changenum = tool.get_changenum(args) else: changenum = None if options.revision_range: diff, parent_diff = tool.diff_between_revisions( options.revision_range, args, repository_info) elif options.svn_changelist: diff, parent_diff = tool.diff_changelist(options.svn_changelist) elif options.diff_filename: parent_diff = None if options.diff_filename == '-': diff = sys.stdin.read() else: try: fp = open(os.path.join(origcwd, options.diff_filename), 'r') diff = fp.read() fp.close() except IOError, e: die("Unable to open diff filename: %s" % e)
def tempt_fate(server, submit_as=None, retries=3): """ Attempts to create a review request on a Review Board server and upload a diff. On success, the review request path is displayed. """ try: if options.rid: # review_request = server.get_ship_it_reviewers(options.rid) review_request = server.get_comment_user(options.rid) else: die("Please give a review id") except APIError, e: if e.error_code == 103: # Not logged in retries = retries - 1 # We had an odd issue where the server ended up a couple of # years in the future. Login succeeds but the cookie date was # "odd" so use of the cookie appeared to fail and eventually # ended up at max recursion depth :-(. Check for a maximum # number of retries. if retries >= 0: server.login(force=True) return tempt_fate(server, submit_as, retries=retries) if options.rid: die("Error getting review request %s: %s" % (options.rid, e))
def get_review_request(self, request_id): """Return the review request resource for the given ID.""" try: request = \ self.root_resource.get_review_requests().get_item(request_id) except APIError, e: die("Error getting review request: %s" % e)
def check_api_version(self): """Checks the API version on the server to determine which to use.""" try: root_resource = self.api_get('api/') rsp = self.api_get(root_resource['links']['info']['href']) self.rb_version = rsp['info']['product']['package_version'] if parse_version(self.rb_version) >= parse_version('1.5.2'): self.deprecated_api = False self.root_resource = root_resource debug('Using the new web API') return True except APIError, e: if e.http_status not in (401, 404): # We shouldn't reach this. If there's a permission denied # from lack of logging in, then the basic auth handler # should have hit it. # # However in some versions it wants you to be logged in # and returns a 401 from the application after you've # done your http basic auth die("Unable to access the root /api/ URL on the server.") return False
def diff_between_revisions(self, revision_range, args, repository_info): """ This doesn't make much sense in Plastic SCM 4. We'll only implement revisions for changests and branches """ die("This option is not supported. Only reviews of a changeset or " "branch are supported")
def credentials_prompt(self, realm, uri, username=None, password=None, *args, **kwargs): """Prompt the user for credentials using the command line. This will prompt the user, and then return the provided username and password. This is used as a callback in the API when the user requires authorization. """ if getattr(self.options, 'diff_filename', None) == '-': die('HTTP authentication is required, but cannot be ' 'used with --diff-filename=-') if username is None or password is None: print print "==> HTTP Authentication Required" print 'Enter authorization information for "%s" at %s' % \ (realm, urlparse(uri)[1]) # getpass will write its prompt to stderr but raw_input # writes to stdout. See bug 2831. if username is None: sys.stderr.write('Username: '******'Password: ') return username, password
def _die(self, msg, e): """Remove the connection to the database and print an error message.""" self.db = None # So that _write_db doesn't cause an exception logging.error( "%s: %s. Try running 'rbt clear-cache' to manually " "clear the HTTP cache for the API.", msg, e) die()
def credentials_prompt(self, realm, uri, username=None, password=None, *args, **kwargs): """Prompt the user for credentials using the command line. This will prompt the user, and then return the provided username and password. This is used as a callback in the API when the user requires authorization. """ if getattr(self.options, "diff_filename", None) == "-": die("HTTP authentication is required, but cannot be " "used with --diff-filename=-") if username is None or password is None: print print "==> HTTP Authentication Required" print 'Enter authorization information for "%s" at %s' % (realm, urlparse(uri)[1]) # getpass will write its prompt to stderr but raw_input # writes to stdout. See bug 2831. if username is None: sys.stderr.write("Username: "******"Password: ") return username, password
def check_gnu_patch(): """Checks if GNU patch is installed, and informs the user if it's not.""" has_gnu_patch = False try: result = execute(['patch', '--version'], ignore_errors=True) has_gnu_patch = 'Free Software Foundation' in result except OSError: pass try: # SunOS has gpatch result = execute(['gpatch', '--version'], ignore_errors=True) has_gnu_patch = 'Free Software Foundation' in result except OSError: pass if not has_gnu_patch: sys.stderr.write('\n') sys.stderr.write('GNU patch is required to post this review.' 'Make sure it is installed and in the path.\n') sys.stderr.write('\n') if os.name == 'nt': sys.stderr.write('On Windows, you can install this from:\n') sys.stderr.write(GNU_PATCH_WIN32_URL) sys.stderr.write('\n') die()
def main(): origcwd = os.path.abspath(os.getcwd()) if 'APPDATA' in os.environ: homepath = os.environ['APPDATA'] elif 'HOME' in os.environ: #homepath = os.environ["HOME"] homepath = '/usr/local/bin/perforce' else: homepath = '' # If we end up creating a cookie file, make sure it's only readable by the # user. os.umask(0077) # Load the config and cookie files cookie_file = os.path.join(homepath, ".post-review-cookies.txt") user_config, globals()['configs'] = load_config_files(homepath) args = parse_options(sys.argv[1:]) debug('RBTools %s' % get_version_string()) debug('Python %s' % sys.version) debug('Running on %s' % (platform.platform())) debug('Home = %s' % homepath) debug('Current Directory = %s' % os.getcwd()) debug('Checking the repository type. Errors shown below are mostly harmless.') repository_info, tool = scan_usable_client(options) debug('Finished checking the repository type.') tool.user_config = user_config tool.configs = configs # Verify that options specific to an SCM Client have not been mis-used. tool.check_options() if repository_info.supports_changesets: changenum = tool.get_changenum(args) else: changenum = None if options.revision_range: diff, parent_diff = tool.diff_between_revisions(options.revision_range, args, repository_info) elif options.svn_changelist: diff, parent_diff = tool.diff_changelist(options.svn_changelist) elif options.diff_filename: parent_diff = None if options.diff_filename == '-': diff = sys.stdin.read() else: try: fp = open(os.path.join(origcwd, options.diff_filename), 'r') diff = fp.read() fp.close() except IOError, e: die("Unable to open diff filename: %s" % e)
def diff_list_resource(server): if options.rid: url = 'api/review-requests/%s/diffs/' % (options.rid) return server.api_get(url) else: die('You must provide a review id (-r #id) to get review comments')
def main(self, *args): """Print the diff to terminal.""" diff = self.get_diff(*args) if not diff: die("There don't seem to be any diffs!") print diff
def check_valid_type(self, close_type): """Check if the user specificed a proper type. Type must either be 'discarded' or 'submitted'. If the type is wrong, the command will stop and alert the user. """ if close_type not in (SUBMITTED, DISCARDED): die("%s is not valid type. Try '%s' or '%s'" % (self.options.close_type, SUBMITTED, DISCARDED))
def get_review_request(self, request_id): """Returns the review request resource for the given ID.""" try: request = \ self.root_resource.get_review_requests().get_item(request_id) except APIError: die('The specified review request does not exist.') return request
def apply_patch(self, patch_file, base_path, base_dir, p=None, revert=False): """Apply the patch and return a PatchResult indicating its success.""" # Figure out the -p argument for patch. We override the calculated # value if it is supplied via a commandline option. p_num = p or self._get_p_number(base_path, base_dir) cmd = ['patch'] if revert: cmd.append('-R') if p_num >= 0: cmd.append('-p%d' % p_num) cmd.extend(['-i', six.text_type(patch_file)]) # Ignore return code 2 in case the patch file consists of only empty # files, which 'patch' can't handle. Other 'patch' errors also give # return code 2, so we must check the command output. rc, patch_output = execute(cmd, extra_ignore_errors=(2, ), return_error_code=True) only_garbage_in_patch = ('patch: **** Only garbage was found in the ' 'patch input.\n') if (patch_output and patch_output.startswith('patch: **** ') and patch_output != only_garbage_in_patch): die('Failed to execute command: %s\n%s' % (cmd, patch_output)) # Check the patch for any added/deleted empty files to handle. if self.supports_empty_files(): try: with open(patch_file, 'rb') as f: patch = f.read() except IOError as e: logging.error('Unable to read file %s: %s', patch_file, e) return patched_empty_files = self.apply_patch_for_empty_files( patch, p_num, revert=revert) # If there are no empty files in a "garbage-only" patch, the patch # is probably malformed. if (patch_output == only_garbage_in_patch and not patched_empty_files): die('Failed to execute command: %s\n%s' % (cmd, patch_output)) # TODO: Should this take into account apply_patch_for_empty_files ? # The return value of that function is False both when it fails # and when there are no empty files. return PatchResult(applied=(rc == 0), patch_output=patch_output)
def get_repository_info(self): if not self.p4.is_supported(): return None p4_info = self.p4.info() repository_path = p4_info.get('Server address', None) if repository_path is None: return None try: parts = repository_path.split(':') hostname = None if len(parts) == 3 and parts[0] == 'ssl': hostname = parts[1] port = parts[2] elif len(parts) == 2: hostname, port = parts if not hostname: die('Path %s is not a valid Perforce P4PORT' % repository_path) info = socket.gethostbyaddr(hostname) # If aliases exist for hostname, create a list of alias:port # strings for repository_path. if info[1]: servers = [info[0]] + info[1] repository_path = [ "%s:%s" % (server, port) for server in servers ] else: repository_path = "%s:%s" % (info[0], port) except (socket.gaierror, socket.herror): pass server_version = p4_info.get('Server version', None) if not server_version: return None m = re.search(r'[^ ]*/([0-9]+)\.([0-9]+)/[0-9]+ .*$', server_version, re.M) if m: self.p4d_version = int(m.group(1)), int(m.group(2)) else: # Gracefully bail if we don't get a match return None # Now that we know it's Perforce, make sure we have GNU diff # installed, and error out if we don't. check_gnu_diff() return RepositoryInfo(path=repository_path, supports_changesets=True)
def get_repository_info(self): """Returns information on the Clear Case repository. This will first check if the cleartool command is installed and in the path, and post-review was run from inside of the view. """ if not check_install('cleartool help'): return None viewname = execute(["cleartool", "pwv", "-short"]).strip() if viewname.startswith('** NONE'): return None # Now that we know it's ClearCase, make sure we have GNU diff installed, # and error out if we don't. check_gnu_diff() property_lines = execute(["cleartool", "lsview", "-full", "-properties", "-cview"], split_lines=True) for line in property_lines: properties = line.split(' ') if properties[0] == 'Properties:': # Determine the view type and check if it's supported. # # Specifically check if webview was listed in properties # because webview types also list the 'snapshot' # entry in properties. if 'webview' in properties: die("Webviews are not supported. You can use post-review" " only in dynamic or snapshot view.") if 'dynamic' in properties: self.viewtype = 'dynamic' else: self.viewtype = 'snapshot' break # Find current VOB's tag vobstag = execute(["cleartool", "describe", "-short", "vob:."], ignore_errors=True).strip() if "Error: " in vobstag: die("To generate diff run post-review inside vob.") # From current working directory cut path to VOB. # VOB's tag contain backslash character before VOB's name. # I hope that first character of VOB's tag like '\new_proj' # won't be treat as new line character but two separate: # backslash and letter 'n' cwd = os.getcwd() base_path = cwd[:cwd.find(vobstag) + len(vobstag)] return ClearCaseRepositoryInfo(path=base_path, base_path=base_path, vobstag=vobstag, supports_parent_diffs=False)
def get_raw_diffs(server): if options.rid: url = '%sr/%s/diff/raw/' % (server.url, options.rid) print url print server.http_get(url) else: die('You must provide a review id (-r #id) to get review comments')
def run_p4(self, p4_args, marshalled=False, password=None, ignore_errors=False, *args, **kwargs): cmd = ['p4'] if marshalled: cmd += ['-G'] if getattr(self.options, 'p4_client', None): cmd += ['-c', self.options.p4_client] if getattr(self.options, 'p4_port', None): cmd += ['-p', self.options.p4_port] if getattr(self.options, 'p4_passwd', None): cmd += ['-P', self.options.p4_passwd] cmd += p4_args if password is not None: cmd += ['-P', password] if marshalled: p = subprocess.Popen(cmd, stdout=subprocess.PIPE) result = [] has_error = False while 1: try: data = marshal.load(p.stdout) except EOFError: break else: result.append(data) if data.get('code', None) == 'error': has_error = True rc = p.wait() if not ignore_errors and (rc or has_error): for record in result: if 'data' in record: print record['data'] die('Failed to execute command: %s\n' % (cmd, )) return result else: result = execute(cmd, ignore_errors=ignore_errors, *args, **kwargs) return result
def _write_db(self): """Flush the contents of the DB to the disk.""" if self.db: try: self.db.commit() except sqlite3.Error as e: logging.error('Could not write database to disk: %s. Try ' 'running "rbt clear-cache" to manually clear ' 'the HTTP cache for the API.', e) die()
def change_status(self, review_request, status): """ Change the status of the review request """ if status not in ['discarded', 'pending', 'submitted']: die("Can't change status of review in %s" % status) self.api_put(review_request['links']['self']['href'], { 'status': status, })
def _write_db(self): """Flush the contents of the DB to the disk.""" if self.db: try: self.db.commit() except sqlite3.Error as e: logging.error( "Could not write database to disk: %s. Try " "running 'rbt clear-cache' to manually clear " "the HTTP cache for the API.", e) die()
def main(self, request_id): """Run the command.""" repository_info, tool = self.initialize_scm_tool() server_url = self.get_server_url(repository_info, tool) self.root_resource = self.get_root(server_url) request = self.get_review_request(request_id) try: draft = request.get_draft() draft = draft.update(public=True) except APIError, e: die("Error publishing review request (it may already be" "publish): %s" % e)
def apply_patch(self, patch_file, base_path, base_dir, p=None, revert=False): """Apply the patch and return a PatchResult indicating its success.""" # Figure out the -p argument for patch. We override the calculated # value if it is supplied via a commandline option. p_num = p or self._get_p_number(base_path, base_dir) cmd = ['patch'] if revert: cmd.append('-R') if p_num >= 0: cmd.append('-p%d' % p_num) cmd.extend(['-i', six.text_type(patch_file)]) # Ignore return code 2 in case the patch file consists of only empty # files, which 'patch' can't handle. Other 'patch' errors also give # return code 2, so we must check the command output. rc, patch_output = execute(cmd, extra_ignore_errors=(2,), return_error_code=True) only_garbage_in_patch = ('patch: **** Only garbage was found in the ' 'patch input.\n') if (patch_output and patch_output.startswith('patch: **** ') and patch_output != only_garbage_in_patch): die('Failed to execute command: %s\n%s' % (cmd, patch_output)) # Check the patch for any added/deleted empty files to handle. if self.supports_empty_files(): try: with open(patch_file, 'rb') as f: patch = f.read() except IOError as e: logging.error('Unable to read file %s: %s', patch_file, e) return patched_empty_files = self.apply_patch_for_empty_files( patch, p_num, revert=revert) # If there are no empty files in a "garbage-only" patch, the patch # is probably malformed. if (patch_output == only_garbage_in_patch and not patched_empty_files): die('Failed to execute command: %s\n%s' % (cmd, patch_output)) # TODO: Should this take into account apply_patch_for_empty_files ? # The return value of that function is False both when it fails # and when there are no empty files. return PatchResult(applied=(rc == 0), patch_output=patch_output)
def parse_config_file(filename): """Parse a .reviewboardrc file. Returns a dictionary containing the configuration from the file. The ``filename`` argument should contain a full path to a .reviewboardrc file. """ config = {} try: execfile(filename, config) except SyntaxError, e: die('Syntax error in config file: %s\n' 'Line %i offset %i\n' % (filename, e.lineno, e.offset))
def main(): origcwd = os.path.abspath(os.getcwd()) if 'APPDATA' in os.environ: homepath = os.environ['APPDATA'] elif 'HOME' in os.environ: homepath = os.environ["HOME"] else: homepath = '' # If we end up creating a cookie file, make sure it's only readable by the # user. os.umask(0077) # Load the config and cookie files cookie_file = os.path.join(homepath, ".post-review-cookies.txt") user_config, globals()['configs'] = load_config_files(homepath) args = parse_options(sys.argv[1:]) debug('RBTools %s' % get_version_string()) debug('Home = %s' % homepath) repository_info, tool = scan_usable_client(options) tool.user_config = user_config tool.configs = configs # Verify that options specific to an SCM Client have not been mis-used. tool.check_options() # Try to find a valid Review Board server to use. if options.server: server_url = options.server else: server_url = tool.scan_for_server(repository_info) if not server_url: print "Unable to find a Review Board server for this source code tree." sys.exit(1) server = ReviewBoardServer(server_url, repository_info, cookie_file) # Handle the case where /api/ requires authorization (RBCommons). if not server.check_api_version(): die("Unable to log in with the supplied username and password.") # Let's begin. server.login() tempt_fate(server)
def get_root(self, server_url): """Returns the root resource of an RBClient.""" cookie_file = self.get_cookie() self.rb_api = RBClient(server_url, cookie_file=cookie_file, username=self.options.username, password=self.options.password, auth_callback=self.credentials_prompt) root = None try: root = self.rb_api.get_root() except ServerInterfaceError, e: die("Could not reach the review board server at %s" % server_url)