import re from typing import List from typing import Optional from typing import Tuple from .connection import Connection from .connection import GiteaHTTPResponse class PullRequest: @classmethod def cmp(cls, entry: dict): if "base" in entry: # a proper pull request return entry["base"]["repo"]["full_name"], entry["number"] else: # an issue without pull request details return entry["repository"]["full_name"], entry["number"] @classmethod def split_id(cls, pr_id: str) -> Tuple[str, str, str]: """ Split /# into individual components and return them in a tuple. """ match = re.match(r"^([^/]+)/([^/]+)#([0-9]+)$", pr_id) if not match: raise ValueError(f"Invalid pull request id: {pr_id}") return match.groups() @classmethod def to_human_readable_string(cls, entry: dict): from osc.output import KeyValueTable from . import User def yes_no(value): return "yes" if value else "no" if "base" in entry: # a proper pull request entry_id = f"{entry['base']['repo']['full_name']}#{entry['number']}" is_pull_request = True else: # an issue without pull request details entry_id = f"{entry['repository']['full_name']}#{entry['number']}" is_pull_request = False # HACK: search API returns issues, the URL needs to be transformed to a pull request URL entry_url = entry["url"] entry_url = re.sub(r"^(.*)/api/v1/repos/(.+/.+)/issues/([0-9]+)$", r"\1/\2/pulls/\3", entry_url) table = KeyValueTable() table.add("ID", entry_id, color="bold") table.add("URL", f"{entry_url}") table.add("Title", f"{entry['title']}") table.add("State", entry["state"]) if is_pull_request: table.add("Draft", yes_no(entry["draft"])) table.add("Merged", yes_no(entry["merged"])) table.add("Allow edit", yes_no(entry["allow_maintainer_edit"])) table.add("Author", f"{User.to_login_full_name_email_string(entry['user'])}") if is_pull_request: table.add("Source", f"{entry['head']['repo']['full_name']}, branch: {entry['head']['ref']}, commit: {entry['head']['sha']}") table.add("Description", entry["body"]) return str(table) @classmethod def list_to_human_readable_string(cls, entries: List, sort: bool = False): if sort: entries = sorted(entries, key=cls.cmp) result = [] for entry in entries: result.append(cls.to_human_readable_string(entry)) return "\n\n".join(result) @classmethod def create( cls, conn: Connection, *, target_owner: str, target_repo: str, target_branch: str, source_owner: str, source_branch: str, title: str, description: Optional[str] = None, ) -> GiteaHTTPResponse: """ Create a pull request to ``owner``/``repo`` to the ``base`` branch. The pull request comes from a fork. The fork repo name is determined from gitea database. :param conn: Gitea ``Connection`` instance. :param target_owner: Owner of the target repo. :param target_repo: Name of the target repo. :param target_branch: Name of the target branch in the target repo. :param source_owner: Owner of the source (forked) repo. :param source_branch: Name of the source branch in the source (forked) repo. :param title: Pull request title. :param description: Pull request description. """ url = conn.makeurl("repos", target_owner, target_repo, "pulls") data = { "base": target_branch, "head": f"{source_owner}:{source_branch}", "title": title, "body": description, } return conn.request("POST", url, json_data=data) @classmethod def get( cls, conn: Connection, owner: str, repo: str, number: int, ) -> GiteaHTTPResponse: """ Get a pull request. :param conn: Gitea ``Connection`` instance. :param owner: Owner of the repo. :param repo: Name of the repo. :param number: Number of the pull request in the repo. """ url = conn.makeurl("repos", owner, repo, "pulls", str(number)) return conn.request("GET", url) @classmethod def set( cls, conn: Connection, owner: str, repo: str, number: int, *, title: Optional[str] = None, description: Optional[str] = None, allow_maintainer_edit: Optional[bool] = None, ) -> GiteaHTTPResponse: """ Change a pull request. :param conn: Gitea ``Connection`` instance. :param owner: Owner of the repo. :param repo: Name of the repo. :param number: Number of the pull request in the repo. :param title: Change pull request title. :param description: Change pull request description. :param allow_maintainer_edit: Change whether users with write access to the base branch can also push to the pull request's head branch. """ json_data = { "title": title, "description": description, "allow_maintainer_edit": allow_maintainer_edit, } url = conn.makeurl("repos", owner, repo, "pulls", str(number)) return conn.request("PATCH", url, json_data=json_data) @classmethod def list( cls, conn: Connection, owner: str, repo: str, *, state: Optional[str] = "open", ) -> GiteaHTTPResponse: """ List pull requests in a repo. :param conn: Gitea ``Connection`` instance. :param owner: Owner of the repo. :param repo: Name of the repo. :param state: Filter by state: open, closed, all. Defaults to open. """ if state == "all": state = None q = { "state": state, } url = conn.makeurl("repos", owner, repo, "pulls", query=q) return conn.request("GET", url) @classmethod def search( cls, conn: Connection, *, state: str = "open", title: Optional[str] = None, owner: Optional[str] = None, labels: Optional[List[str]] = None, assigned: bool = False, created: bool = False, mentioned: bool = False, review_requested: bool = False, ) -> GiteaHTTPResponse: """ Search pull requests. :param conn: Gitea ``Connection`` instance. :param state: Filter by state: open, closed. Defaults to open. :param title: Filter by substring in title. :param owner: Filter by owner of the repository associated with the pull requests. :param labels: Filter by associated labels. Non existent labels are discarded. :param assigned: Filter pull requests assigned to you. :param created: Filter pull requests created by you. :param mentioned: Filter pull requests mentioning you. :param review_requested: Filter pull requests requesting your review. """ q = { "type": "pulls", "state": state, "q": title, "owner": owner, "labels": ",".join(labels) if labels else None, "assigned": assigned, "created": created, "mentioned": mentioned, "review_requested": review_requested, } url = conn.makeurl("repos", "issues", "search", query=q) return conn.request("GET", url) @classmethod def get_patch( cls, conn: Connection, owner: str, repo: str, number: str, ) -> GiteaHTTPResponse: """ Get a patch associated with a pull request. :param conn: Gitea ``Connection`` instance. :param owner: Owner of the repo. :param repo: Name of the repo. :param number: Number of the pull request in the repo. """ url = conn.makeurl("repos", owner, repo, "pulls", f"{number}.patch") return conn.request("GET", url)