From fbfc3414cea6341eed961f4e38d7ecfb85d67a13 Mon Sep 17 00:00:00 2001 From: Ondrej Nosek Date: Jan 17 2020 10:49:49 +0000 Subject: Create fork of the active repository Adds new command 'fork' that call API method which forks active repository for the given (or active) user and creates remote record in git configuration. Fixes: #276 Signed-off-by: Ondrej Nosek --- diff --git a/conf/etc/rpkg/fedpkg-stage.conf b/conf/etc/rpkg/fedpkg-stage.conf index a5324db..0179fb4 100644 --- a/conf/etc/rpkg/fedpkg-stage.conf +++ b/conf/etc/rpkg/fedpkg-stage.conf @@ -62,3 +62,7 @@ url = https://pdc.stg.fedoraproject.org/ [fedpkg-stage.greenwave] url = https://greenwave.stg.fedoraproject.org/ + +[fedpkg-stage.distgit] +apibaseurl = https://src.stg.fedoraproject.org +token = diff --git a/conf/etc/rpkg/fedpkg.conf b/conf/etc/rpkg/fedpkg.conf index d181940..1154b69 100644 --- a/conf/etc/rpkg/fedpkg.conf +++ b/conf/etc/rpkg/fedpkg.conf @@ -63,3 +63,7 @@ url = https://pdc.fedoraproject.org/ [fedpkg.greenwave] url = https://greenwave.fedoraproject.org/ + +[fedpkg.distgit] +apibaseurl = https://src.fedoraproject.org +token = diff --git a/fedpkg/cli.py b/fedpkg/cli.py index f3c6c04..785b9ca 100644 --- a/fedpkg/cli.py +++ b/fedpkg/cli.py @@ -31,10 +31,12 @@ from six.moves.configparser import NoOptionError from six.moves.urllib_parse import urlparse from pyrpkg import rpkgError from fedpkg.bugzilla import BugzillaClient -from fedpkg.utils import ( - get_fedora_release_state, get_release_branches, sl_list_to_dict, verify_sls, - new_pagure_issue, get_pagure_token, is_epel, assert_valid_epel_package, - assert_new_tests_repo, get_dist_git_url, get_stream_branches, expand_release) +from fedpkg.utils import (assert_new_tests_repo, assert_valid_epel_package, + do_fork, expand_release, get_dist_git_url, + get_fedora_release_state, get_distgit_token, + get_pagure_token, get_release_branches, + get_stream_branches, is_epel, new_pagure_issue, + sl_list_to_dict, verify_sls) RELEASE_BRANCH_REGEX = r'^(f\d+|el\d+|epel\d+)$' LOCAL_PACKAGE_CONFIG = 'package.cfg' @@ -120,6 +122,7 @@ class fedpkgClient(cliClient): self.register_request_repo() self.register_request_tests_repo() self.register_request_branch() + self.register_do_fork() self.register_override() # Target registry goes here @@ -260,9 +263,9 @@ class fedpkgClient(cliClient): description = textwrap.dedent(''' Request a new dist-git repository - Before requesting a new dist-git repository for a new package, you need to - generate a pagure.io API token at https://{1}/settings/token/new, select the - "Create a new ticket" ACL and save it in your local user configuration located + Before the operation, you need to generate a pagure.io API token at + https://{1}/settings/token/new, select the relevant ACL(s) + and save it in your local user configuration located at ~/.config/rpkg/{0}.conf. For example: [{0}.pagure] @@ -343,7 +346,7 @@ class fedpkgClient(cliClient): Below is a basic example of the command to request a dist-git repository for the space tests/foo: - fedpkg request-tests-repo foo "Description of the repository" + {0} request-tests-repo foo "Description of the repository" Note that the space name needs to reflect the intent of the tests and will undergo a manual review. @@ -367,8 +370,8 @@ class fedpkgClient(cliClient): description = textwrap.dedent(''' Request a new dist-git branch - Please refer to the request-repo command to see what has to be done before - requesting a dist-git branch. + Please refer to the help of the request-repo command to see what has + to be done before requesting a dist-git branch. Branch name could be one of current active Fedora and EPEL releases. Use command ``{0} releases-info`` to get release names that can be used to request @@ -437,6 +440,44 @@ class fedpkgClient(cliClient): ) request_branch_parser.set_defaults(command=self.request_branch) + def register_do_fork(self): + help_msg = 'Create a new fork of the current repository' + description = textwrap.dedent(''' + Create a new fork of the current repository + + Before the operation, you need to generate an API token at + https://{1}/settings/token/new, select the relevant ACL(s) + and save it in your local user configuration located + at ~/.config/rpkg/{0}.conf. For example: + + [{0}.distgit] + token = + + Below is a basic example of the command to fork a current repository: + + {0} fork + + Operation requires username (FAS_ID). by default, current logged + username is taken. It could be overridden by reusing an argument: + + {0} --user FAS_ID fork + '''.format(self.name, urlparse(self.config.get( + '{0}.distgit'.format(self.name), 'apibaseurl')).netloc)) + + fork_parser = self.subparsers.add_parser( + 'fork', + formatter_class=argparse.RawDescriptionHelpFormatter, + help=help_msg, + description=description) + fork_parser.add_argument( + '--namespace', + required=False, + default='rpms', + choices=self.get_distgit_namespaces(), + dest='fork_namespace', + help='Namespace of the fork. If omitted, default to rpms.') + fork_parser.set_defaults(command=self.do_distgit_fork) + def register_releases_info(self): help_msg = 'Print Fedora or EPEL current active releases' parser = self.subparsers.add_parser( @@ -1052,6 +1093,29 @@ class fedpkgClient(cliClient): config=config, ) + def do_distgit_fork(self): + """create fork of the distgit repository""" + distgit_api_base_url = self.config.get('{0}.distgit'.format(self.name), "apibaseurl") + distgit_remote_base_url = self.config.get( + '{0}'.format(self.name), + "gitbaseurl", + vars={'user': 'any', 'repo': 'any'}, + ) + distgit_token = get_distgit_token(self.config, self.name) + + fork_url = do_fork( + base_url=distgit_api_base_url, + remote_base_url=distgit_remote_base_url, + token=distgit_token, + username=self.cmd.user, + repo=self.cmd.repo, + namespace=self.args.fork_namespace, + cli_name=self.name, + ) + if fork_url: + msg = "Fork of the repository has been created: {0}" + self.log.info(msg.format(fork_url)) + def create_buildroot_override(self): """Create a buildroot override in Bodhi""" check_bodhi_version() diff --git a/fedpkg/utils.py b/fedpkg/utils.py index fd5263f..c84b6b4 100644 --- a/fedpkg/utils.py +++ b/fedpkg/utils.py @@ -10,7 +10,9 @@ # option) any later version. See http://www.gnu.org/copyleft/gpl.html for # the full text of the license. +import git import json +import os import re from datetime import datetime @@ -134,6 +136,76 @@ def new_pagure_issue(url, token, title, body, cli_name): url.rstrip('/'), rv.json()['issue']['id']) +def do_fork(base_url, remote_base_url, token, username, repo, namespace, cli_name): + """ + Creates a fork of the project. + :param base_url: a string of the URL repository + :param remote_base_url: a string of the remote tracked repository + :param token: a string of the API token that has rights to make a fork + :param username: a string of the (FAS) user name + :param repo: object, current project git repository + :param namespace: a string determines a type of the repository + :param cli_name: string of the CLI's name (e.g. fedpkg) + :return: a string of the URL to the created fork in the UI + """ + api_url = '{0}/api/0'.format(base_url.rstrip('/')) + fork_url = '{0}/fork'.format(api_url) + + repo_name = os.path.basename(repo.working_dir) + + parsed_url = urlparse(remote_base_url) + remote_url = '{0}://{1}/forks/{2}/{3}/{4}.git'.format( + parsed_url.scheme, + parsed_url.netloc, + username, + namespace, + repo_name, + ) + + headers = { + 'Authorization': 'token {0}'.format(token), + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + payload = json.dumps({ + 'wait': True, + 'namespace': namespace, + 'repo': repo_name, + }) + try: + rv = requests.post( + fork_url, headers=headers, data=payload, timeout=60) + except ConnectionError as error: + error_msg = ('The connection to API failed while trying to ' + 'create a new fork. The error was: {0}'.format(str(error))) + raise rpkgError(error_msg) + + base_error_msg = ('The following error occurred while creating a new fork: {0}') + if not rv.ok: + # Lets see if the API returned an error message in JSON that we can + # show the user + try: + rv_error = rv.json().get('error') + except ValueError: + rv_error = rv.text + # show hint for expired token + if re.search(r"Invalid or expired token", rv_error, re.IGNORECASE): + base_error_msg += '\nFor invalid or expired token refer to ' \ + '"{0} fork -h" to set a token in your user ' \ + 'configuration.'.format(cli_name) + raise rpkgError(base_error_msg.format(rv_error)) + + try: + repo.create_remote(username, url=remote_url) + except git.exc.GitCommandError as e: + error_msg = "Fork was created; during create remote:\n {0}\n {1}".format( + " ".join(e.command), e.stderr) + raise rpkgError(error_msg) + + return '{0}/fork/{1}/{2}'.format( + base_url.rstrip('/'), username, repo_name) + + def get_release_branches(server_url): """ Get the active Fedora release branches from PDC @@ -232,8 +304,24 @@ def get_pagure_token(config, cli_name): return config.get(conf_section, 'token') except (NoSectionError, NoOptionError): raise rpkgError( - 'Missing a Pagure token. Refer to "{0} request-repo -h" to set a ' - 'token in your user configuration.'.format(cli_name)) + 'Missing a Pagure token. Refer to the help of the current command ' + '(-h/--help)" to set a token in your user configuration.') + + +def get_distgit_token(config, cli_name): + """ + Gets the distgit token configured in the user's configuration file + :param config: ConfigParser object + :param cli_name: string of the CLI's name (e.g. fedpkg) + :return: string of the distgit token + """ + conf_section = '{0}.distgit'.format(cli_name) + try: + return config.get(conf_section, 'token') + except (NoSectionError, NoOptionError): + raise rpkgError( + 'Missing a distgit token. Refer to the help of the current command ' + '(-h/--help)" to set a token in your user configuration.') def is_epel(branch):