From e23c566b0df1974c51f7566c367be1bab3ace23c Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 15 Jan 2025 17:01:47 +0100 Subject: [PATCH] Change 'git-obs' to use owner/repo[#pull] arguments consistently --- behave/features/git-pr.feature | 6 +-- behave/features/git-repo-clone.feature | 5 ++- behave/features/git-repo-fork.feature | 26 +++++++++---- osc/commandline_git.py | 47 ++++++++++++++++++++--- osc/commands_git/pr_get.py | 17 ++++----- osc/commands_git/pr_list.py | 18 +++++---- osc/commands_git/repo_clone.py | 53 ++++++++++++++++++++------ osc/commands_git/repo_fork.py | 47 ++++++++++++++++------- osc/gitea_api/fork.py | 2 +- osc/gitea_api/pr.py | 2 +- osc/gitea_api/repo.py | 12 ++++++ 11 files changed, 172 insertions(+), 63 deletions(-) diff --git a/behave/features/git-pr.feature b/behave/features/git-pr.feature index 28e70798b..e5793811f 100644 --- a/behave/features/git-pr.feature +++ b/behave/features/git-pr.feature @@ -3,8 +3,8 @@ Feature: `git-obs pr` command Background: Given I set working directory to "{context.osc.temp}" - And I execute git-obs with args "repo fork pool test-GitPkgA" - And I execute git-obs with args "repo clone Admin test-GitPkgA --no-ssh-strict-host-key-checking" + And I execute git-obs with args "repo fork pool/test-GitPkgA" + And I execute git-obs with args "repo clone Admin/test-GitPkgA --no-ssh-strict-host-key-checking" And I set working directory to "{context.osc.temp}/test-GitPkgA" And I execute "sed -i 's@^\(Version.*\)@\1.1@' *.spec" And I execute "git commit -m 'Change version' -a" @@ -14,7 +14,7 @@ Background: @destructive Scenario: List pull requests - When I execute git-obs with args "pr list pool test-GitPkgA" + When I execute git-obs with args "pr list pool/test-GitPkgA" Then the exit code is 0 And stdout matches """ diff --git a/behave/features/git-repo-clone.feature b/behave/features/git-repo-clone.feature index ae2235b07..dc0cf6ca6 100644 --- a/behave/features/git-repo-clone.feature +++ b/behave/features/git-repo-clone.feature @@ -7,7 +7,7 @@ Background: @destructive Scenario: Clone a git repo - When I execute git-obs with args "repo clone pool test-GitPkgA --no-ssh-strict-host-key-checking" + When I execute git-obs with args "repo clone pool/test-GitPkgA --no-ssh-strict-host-key-checking" Then the exit code is 0 And stdout is """ @@ -20,5 +20,8 @@ Scenario: Clone a git repo * URL: http://localhost:{context.podman.container.ports[gitea_http]} * User: Admin + Cloning git repo pool/test-GitPkgA ... Cloning into 'test-GitPkgA'... + + Total cloned repos: 1 """ diff --git a/behave/features/git-repo-fork.feature b/behave/features/git-repo-fork.feature index 516f7398f..1edba94b8 100644 --- a/behave/features/git-repo-fork.feature +++ b/behave/features/git-repo-fork.feature @@ -7,7 +7,7 @@ Background: @destructive Scenario: Fork a git repo - When I execute git-obs with args "repo fork pool test-GitPkgA" + When I execute git-obs with args "repo fork pool/test-GitPkgA" Then the exit code is 0 And stdout is """ @@ -22,12 +22,14 @@ Scenario: Fork a git repo Forking git repo pool/test-GitPkgA ... * Fork created: Admin/test-GitPkgA + + Total forked repos: 1 """ @destructive Scenario: Fork a git repo twice under different names - When I execute git-obs with args "repo fork pool test-GitPkgA" + When I execute git-obs with args "repo fork pool/test-GitPkgA" Then the exit code is 0 And stdout is """ @@ -42,8 +44,10 @@ Scenario: Fork a git repo twice under different names Forking git repo pool/test-GitPkgA ... * Fork created: Admin/test-GitPkgA + + Total forked repos: 1 """ - When I execute git-obs with args "repo fork pool test-GitPkgA --new-repo-name=new-package" + When I execute git-obs with args "repo fork pool/test-GitPkgA --new-repo-name=new-package" Then the exit code is 0 And stdout is """ @@ -59,12 +63,14 @@ Scenario: Fork a git repo twice under different names Forking git repo pool/test-GitPkgA ... * Fork already exists: Admin/test-GitPkgA * WARNING: Using an existing fork with a different name than requested + + Total forked repos: 1 """ @destructive Scenario: Fork a git repo from pool and fork someone else's fork of the same repo - When I execute git-obs with args "repo fork pool test-GitPkgA" + When I execute git-obs with args "repo fork pool/test-GitPkgA" Then the exit code is 0 And stdout is """ @@ -79,8 +85,10 @@ Scenario: Fork a git repo from pool and fork someone else's fork of the same rep Forking git repo pool/test-GitPkgA ... * Fork created: Admin/test-GitPkgA + + Total forked repos: 1 """ - When I execute git-obs with args "repo fork -G alice pool test-GitPkgA --new-repo-name=test-GitPkgA-alice" + When I execute git-obs with args "repo fork -G alice pool/test-GitPkgA --new-repo-name=test-GitPkgA-alice" Then the exit code is 0 And stdout is """ @@ -95,9 +103,11 @@ Scenario: Fork a git repo from pool and fork someone else's fork of the same rep Forking git repo pool/test-GitPkgA ... * Fork created: Alice/test-GitPkgA-alice + + Total forked repos: 1 """ # this succeeds with 202 and the requested fork is NOT created - When I execute git-obs with args "repo fork Alice test-GitPkgA-alice" + When I execute git-obs with args "repo fork Alice/test-GitPkgA-alice" Then the exit code is 0 And stdout is """ @@ -112,6 +122,8 @@ Scenario: Fork a git repo from pool and fork someone else's fork of the same rep Forking git repo Alice/test-GitPkgA-alice ... * Fork created: Admin/test-GitPkgA-alice + + Total forked repos: 1 """ - When I execute git-obs with args "repo clone Admin test-GitPkgA-alice --no-ssh-strict-host-key-checking" + When I execute git-obs with args "repo clone Admin/test-GitPkgA-alice --no-ssh-strict-host-key-checking" Then the exit code is 0 diff --git a/osc/commandline_git.py b/osc/commandline_git.py index 08be76d08..4dcb10c75 100644 --- a/osc/commandline_git.py +++ b/osc/commandline_git.py @@ -1,3 +1,4 @@ +import argparse import os import subprocess import sys @@ -8,6 +9,36 @@ from .output import print_msg +class OwnerRepoAction(argparse.Action): + def __call__(self, parser, namespace, value, option_string=None): + from . import gitea_api + + try: + if isinstance(value, list): + namespace_value = [gitea_api.Repo.split_id(i) for i in value] + else: + namespace_value = gitea_api.Repo.split_id(value) + except ValueError as e: + raise argparse.ArgumentError(self, str(e)) + + setattr(namespace, self.dest, namespace_value) + + +class OwnerRepoPullAction(argparse.Action): + def __call__(self, parser, namespace, value, option_string=None): + from . import gitea_api + + try: + if isinstance(value, list): + namespace_value = [gitea_api.PullRequest.split_id(i) for i in value] + else: + namespace_value = gitea_api.PullRequest.split_id(value) + except ValueError as e: + raise argparse.ArgumentError(self, str(e)) + + setattr(namespace, self.dest, namespace_value) + + class GitObsCommand(osc.commandline_common.Command): @property def gitea_conf(self): @@ -29,16 +60,20 @@ def print_gitea_settings(self): print(f" * User: {self.gitea_login.user}", file=sys.stderr) print("", file=sys.stderr) - def add_argument_owner(self): + def add_argument_owner_repo(self, **kwargs): self.add_argument( - "owner", - help="Name of the repository owner (login, org)", + "owner_repo", + action=OwnerRepoAction, + help="Owner and repo: (format: /)", + **kwargs, ) - def add_argument_repo(self): + def add_argument_owner_repo_pull(self, **kwargs): self.add_argument( - "repo", - help="Name of the repository", + "owner_repo_pull", + action=OwnerRepoPullAction, + help="Owner, repo and pull request number (format: /#)", + **kwargs, ) def add_argument_new_repo_name(self): diff --git a/osc/commands_git/pr_get.py b/osc/commands_git/pr_get.py index a7b4f2d35..1db77abde 100644 --- a/osc/commands_git/pr_get.py +++ b/osc/commands_git/pr_get.py @@ -13,11 +13,7 @@ class PullRequestGetCommand(osc.commandline_git.GitObsCommand): parent = "PullRequestCommand" def init_arguments(self): - self.add_argument( - "id", - nargs="+", - help="Pull request ID in /# format", - ) + self.add_argument_owner_repo_pull(nargs="+") self.add_argument( "-p", "--patch", @@ -34,14 +30,13 @@ def run(self, args): num_entries = 0 failed_entries = [] - for pr_id in args.id: - owner, repo, number = gitea_api.PullRequest.split_id(pr_id) + for owner, repo, pull in args.owner_repo_pull: try: - pr = gitea_api.PullRequest.get(self.gitea_conn, owner, repo, number).json() + pr = gitea_api.PullRequest.get(self.gitea_conn, owner, repo, pull).json() num_entries += 1 except gitea_api.GiteaException as e: if e.status == 404: - failed_entries.append(pr_id) + failed_entries.append(f"{owner}/{repo}#{pull}") continue raise print(gitea_api.PullRequest.to_human_readable_string(pr)) @@ -49,10 +44,12 @@ def run(self, args): if args.patch: print("") print(tty.colorize("Patch:", "bold")) - patch = gitea_api.PullRequest.get_patch(self.gitea_conn, owner, repo, number).data + patch = gitea_api.PullRequest.get_patch(self.gitea_conn, owner, repo, pull).data patch = highlight_diff(patch) print(patch.decode("utf-8")) + print() + print(f"Total entries: {num_entries}", file=sys.stderr) if failed_entries: print( diff --git a/osc/commands_git/pr_list.py b/osc/commands_git/pr_list.py index 596cbef2e..3d3ed228d 100644 --- a/osc/commands_git/pr_list.py +++ b/osc/commands_git/pr_list.py @@ -12,8 +12,7 @@ class PullRequestListCommand(osc.commandline_git.GitObsCommand): parent = "PullRequestCommand" def init_arguments(self): - self.add_argument_owner() - self.add_argument_repo() + self.add_argument_owner_repo(nargs="+") self.add_argument( "--state", choices=["open", "closed", "all"], @@ -26,11 +25,14 @@ def run(self, args): self.print_gitea_settings() - data = gitea_api.PullRequest.list(self.gitea_conn, args.owner, args.repo, state=args.state).json() + total_entries = 0 + for owner, repo in args.owner_repo: + data = gitea_api.PullRequest.list(self.gitea_conn, owner, repo, state=args.state).json() + total_entries += len(data) - text = gitea_api.PullRequest.list_to_human_readable_string(data, sort=True) - if text: - print(text) - print("", file=sys.stderr) + text = gitea_api.PullRequest.list_to_human_readable_string(data, sort=True) + if text: + print(text) + print("", file=sys.stderr) - print(f"Total entries: {len(data)}", file=sys.stderr) + print(f"Total entries: {total_entries}", file=sys.stderr) diff --git a/osc/commands_git/repo_clone.py b/osc/commands_git/repo_clone.py index d4d00b96b..9e9cd7d6f 100644 --- a/osc/commands_git/repo_clone.py +++ b/osc/commands_git/repo_clone.py @@ -1,3 +1,6 @@ +import subprocess +import sys + import osc.commandline_git @@ -13,8 +16,7 @@ class RepoCloneCommand(osc.commandline_git.GitObsCommand): parent = "RepoCommand" def init_arguments(self): - self.add_argument_owner() - self.add_argument_repo() + self.add_argument_owner_repo(nargs="+") self.add_argument( "-a", @@ -45,15 +47,42 @@ def init_arguments(self): def run(self, args): from osc import gitea_api + from osc.output import tty + self.print_gitea_settings() - gitea_api.Repo.clone( - self.gitea_conn, - args.owner, - args.repo, - directory=args.directory, - anonymous=args.anonymous, - add_remotes=True, - ssh_private_key_path=args.ssh_key or self.gitea_login.ssh_key, - ssh_strict_host_key_checking=not(args.no_ssh_strict_host_key_checking), - ) + if len(args.owner_repo) > 1 and args.directory: + self.parser.error("The --directory option cannot be used with multiple repos") + + num_entries = 0 + failed_entries = [] + for owner, repo in args.owner_repo: + print(f"Cloning git repo {owner}/{repo} ...", file=sys.stderr) + try: + gitea_api.Repo.clone( + self.gitea_conn, + owner, + repo, + directory=args.directory, + anonymous=args.anonymous, + add_remotes=True, + ssh_private_key_path=args.ssh_key or self.gitea_login.ssh_key, + ssh_strict_host_key_checking=not(args.no_ssh_strict_host_key_checking), + ) + num_entries += 1 + except gitea_api.GiteaException as e: + if e.status == 404: + print(f" * {tty.colorize('ERROR', 'red,bold')}: Repo doesn't exist: {owner}/{repo}", file=sys.stderr) + failed_entries.append(f"{owner}/{repo}") + continue + raise + except subprocess.CalledProcessError as e: + print(f" * {tty.colorize('ERROR', 'red,bold')}: git clone failed", file=sys.stderr) + failed_entries.append(f"{owner}/{repo}") + continue + + print("", file=sys.stderr) + print(f"Total cloned repos: {num_entries}", file=sys.stderr) + if failed_entries: + print(f"{tty.colorize('ERROR', 'red,bold')}: Couldn't clone the following repos: {', '.join(failed_entries)}", file=sys.stderr) + sys.exit(1) diff --git a/osc/commands_git/repo_fork.py b/osc/commands_git/repo_fork.py index b05e55167..8d90e2cd1 100644 --- a/osc/commands_git/repo_fork.py +++ b/osc/commands_git/repo_fork.py @@ -12,8 +12,7 @@ class RepoForkCommand(osc.commandline_git.GitObsCommand): parent = "RepoCommand" def init_arguments(self): - self.add_argument_owner() - self.add_argument_repo() + self.add_argument_owner_repo(nargs="+") self.add_argument_new_repo_name() def run(self, args): @@ -22,15 +21,35 @@ def run(self, args): self.print_gitea_settings() - print(f"Forking git repo {args.owner}/{args.repo} ...", file=sys.stderr) - try: - response = gitea_api.Fork.create(self.gitea_conn, args.owner, args.repo, new_repo_name=args.new_repo_name) - repo = response.json() - fork_owner = repo["owner"]["login"] - fork_repo = repo["name"] - print(f" * Fork created: {fork_owner}/{fork_repo}", file=sys.stderr) - except gitea_api.ForkExists as e: - fork_owner = e.fork_owner - fork_repo = e.fork_repo - print(f" * Fork already exists: {fork_owner}/{fork_repo}", file=sys.stderr) - print(f" * {tty.colorize('WARNING', 'yellow,bold')}: Using an existing fork with a different name than requested", file=sys.stderr) + if len(args.owner_repo) > 1 and args.new_repo_name: + self.parser.error("The --new-repo-name option cannot be used with multiple repos") + + num_entries = 0 + failed_entries = [] + for owner, repo in args.owner_repo: + print(f"Forking git repo {owner}/{repo} ...", file=sys.stderr) + try: + response = gitea_api.Fork.create(self.gitea_conn, owner, repo, new_repo_name=args.new_repo_name) + repo = response.json() + fork_owner = repo["owner"]["login"] + fork_repo = repo["name"] + print(f" * Fork created: {fork_owner}/{fork_repo}", file=sys.stderr) + num_entries += 1 + except gitea_api.ForkExists as e: + fork_owner = e.fork_owner + fork_repo = e.fork_repo + print(f" * Fork already exists: {fork_owner}/{fork_repo}", file=sys.stderr) + print(f" * {tty.colorize('WARNING', 'yellow,bold')}: Using an existing fork with a different name than requested", file=sys.stderr) + num_entries += 1 + except gitea_api.GiteaException as e: + if e.status == 404: + print(f" * {tty.colorize('ERROR', 'red,bold')}: Repo doesn't exist: {owner}/{repo}", file=sys.stderr) + failed_entries.append(f"{owner}/{repo}") + continue + raise + + print("", file=sys.stderr) + print(f"Total forked repos: {num_entries}", file=sys.stderr) + if failed_entries: + print(f"{tty.colorize('ERROR', 'red,bold')}: Couldn't fork the following repos: {', '.join(failed_entries)}", file=sys.stderr) + sys.exit(1) diff --git a/osc/gitea_api/fork.py b/osc/gitea_api/fork.py index 916ca5da8..b59cc57e4 100644 --- a/osc/gitea_api/fork.py +++ b/osc/gitea_api/fork.py @@ -56,8 +56,8 @@ def create( return conn.request("POST", url, json_data=json_data) except GiteaException as e: # use ForkExists exception to parse fork_owner and fork_repo from the response - fork_exists_exception = ForkExists(e.response, owner, repo) if e.status == 409: + fork_exists_exception = ForkExists(e.response, owner, repo) if exist_ok: from . import Repo return Repo.get(conn, fork_exists_exception.fork_owner, fork_exists_exception.fork_repo) diff --git a/osc/gitea_api/pr.py b/osc/gitea_api/pr.py index 0385ee91c..4b91fc15d 100644 --- a/osc/gitea_api/pr.py +++ b/osc/gitea_api/pr.py @@ -22,7 +22,7 @@ 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"(.+)/(.+)#(.+)", pr_id) + match = re.match(r"^([^/]+)/([^/]+)#([0-9]+)$", pr_id) if not match: raise ValueError(f"Invalid pull request id: {pr_id}") return match.groups() diff --git a/osc/gitea_api/repo.py b/osc/gitea_api/repo.py index da7bb95c2..10607322d 100644 --- a/osc/gitea_api/repo.py +++ b/osc/gitea_api/repo.py @@ -1,6 +1,8 @@ import os +import re import subprocess from typing import Optional +from typing import Tuple from .connection import Connection from .connection import GiteaHTTPResponse @@ -8,6 +10,16 @@ class Repo: + @classmethod + def split_id(cls, repo_id: str) -> Tuple[str, str]: + """ + Split / into individual components and return them in a tuple. + """ + match = re.match(r"^([^/]+)/([^/]+)$", repo_id) + if not match: + raise ValueError(f"Invalid repo id: {repo_id}") + return match.groups() + @classmethod def get( cls,