mirror of
https://github.com/openSUSE/osc.git
synced 2025-10-31 19:42:16 +01:00
Improve handling exceptions in 'git-obs' command
This commit is contained in:
@@ -3,6 +3,8 @@ from .exceptions import BranchDoesNotExist
|
||||
from .exceptions import BranchExists
|
||||
from .exceptions import ForkExists
|
||||
from .exceptions import GiteaException
|
||||
from .exceptions import RepoExists
|
||||
from .exceptions import response_to_exception
|
||||
from .branch import Branch
|
||||
from .conf import Config
|
||||
from .conf import Login
|
||||
|
||||
@@ -25,12 +25,7 @@ class Branch:
|
||||
:param branch: Name of the branch.
|
||||
"""
|
||||
url = conn.makeurl("repos", owner, repo, "branches", branch)
|
||||
try:
|
||||
return conn.request("GET", url)
|
||||
except GiteaException as e:
|
||||
if e.status == 404:
|
||||
raise BranchDoesNotExist(e.response, owner, repo, branch) from None
|
||||
raise
|
||||
return conn.request("GET", url, context={"owner": owner, "repo": repo})
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
@@ -77,10 +72,8 @@ class Branch:
|
||||
}
|
||||
url = conn.makeurl("repos", owner, repo, "branches")
|
||||
try:
|
||||
return conn.request("POST", url, json_data=json_data)
|
||||
except GiteaException as e:
|
||||
if e.status == 409:
|
||||
if exist_ok:
|
||||
return cls.get(conn, owner, repo, new_branch_name)
|
||||
raise BranchExists(e.response, owner, repo, new_branch_name) from None
|
||||
return conn.request("POST", url, json_data=json_data, context={"owner": owner, "repo": repo, "branch": new_branch_name})
|
||||
except BranchExists as e:
|
||||
if not exist_ok:
|
||||
raise
|
||||
return cls.get(conn, owner, repo, new_branch_name)
|
||||
|
||||
@@ -96,9 +96,13 @@ class Connection:
|
||||
url_query_str = urllib.parse.urlencode(query, doseq=True)
|
||||
return urllib.parse.urlunsplit(("", "", url_path_str, url_query_str, ""))
|
||||
|
||||
def request(self, method, url, json_data: Optional[dict] = None) -> GiteaHTTPResponse:
|
||||
def request(
|
||||
self, method, url, json_data: Optional[dict] = None, *, context: Optional[dict] = None
|
||||
) -> GiteaHTTPResponse:
|
||||
"""
|
||||
Make a request and return ``GiteaHTTPResponse``.
|
||||
|
||||
:param context: Additional parameters passed as **kwargs to an exception if raised
|
||||
"""
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
@@ -134,18 +138,18 @@ class Connection:
|
||||
self.conn.close()
|
||||
|
||||
if isinstance(response, http.client.HTTPResponse):
|
||||
result = GiteaHTTPResponse(urllib3.response.HTTPResponse.from_httplib(response))
|
||||
response = GiteaHTTPResponse(urllib3.response.HTTPResponse.from_httplib(response))
|
||||
else:
|
||||
result = GiteaHTTPResponse(response)
|
||||
response = GiteaHTTPResponse(response)
|
||||
|
||||
if not hasattr(response, "status"):
|
||||
from .exceptions import GiteaException # pylint: disable=import-outside-toplevel,cyclic-import
|
||||
|
||||
raise GiteaException(result)
|
||||
raise GiteaException(response)
|
||||
|
||||
if response.status // 100 != 2:
|
||||
from .exceptions import GiteaException # pylint: disable=import-outside-toplevel,cyclic-import
|
||||
from .exceptions import response_to_exception
|
||||
|
||||
raise GiteaException(result)
|
||||
raise response_to_exception(response, context=context)
|
||||
|
||||
return result
|
||||
return response
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
import inspect
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from .. import oscerr
|
||||
from .connection import GiteaHTTPResponse
|
||||
|
||||
|
||||
def response_to_exception(response: GiteaHTTPResponse, *, context: Optional[dict] = None):
|
||||
"""
|
||||
Throw an appropriate exception based on the contents of ``response``.
|
||||
Raise generic ``GiteaException`` if no exception matches the ``response``.
|
||||
|
||||
Caveats:
|
||||
- Gitea doesn't return any machine parseable data, everything is plain text
|
||||
- the errors are sometimes described in the ``message``, sometimes in the list of ``errors``
|
||||
- in some cases, it's required to provide additional context to the request, that is passed to the raised exception,
|
||||
for example: ``conn.request("GET", url, context={"owner": owner, "repo": repo})``
|
||||
"""
|
||||
data = response.json()
|
||||
messages = [data["message"]] + data.get("errors", [])
|
||||
|
||||
for cls in EXCEPTION_CLASSES:
|
||||
if cls.RESPONSE_STATUS is not None and cls.RESPONSE_STATUS != response.status:
|
||||
continue
|
||||
|
||||
for regex in cls.RESPONSE_MESSAGE_RE:
|
||||
for message in messages:
|
||||
match = regex.match(message)
|
||||
if match:
|
||||
kwargs = context.copy() if context else {}
|
||||
kwargs.update(match.groupdict())
|
||||
return cls(response, **kwargs)
|
||||
|
||||
return GiteaException(response)
|
||||
|
||||
|
||||
class GiteaException(oscerr.OscBaseError):
|
||||
RESPONSE_STATUS: Optional[int] = None
|
||||
RESPONSE_MESSAGE_RE: list
|
||||
|
||||
def __init__(self, response: GiteaHTTPResponse):
|
||||
self.response = response
|
||||
|
||||
@@ -24,6 +58,12 @@ class GiteaException(oscerr.OscBaseError):
|
||||
|
||||
|
||||
class BranchDoesNotExist(GiteaException):
|
||||
RESPONSE_STATUS = 404
|
||||
# modules/git/error.go: return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
|
||||
RESPONSE_MESSAGE_RE = [
|
||||
re.compile(r"branch does not exist \[name: (?P<branch>.+)\]"),
|
||||
]
|
||||
|
||||
def __init__(self, response: GiteaHTTPResponse, owner: str, repo: str, branch: str):
|
||||
super().__init__(response)
|
||||
self.owner = owner
|
||||
@@ -36,6 +76,14 @@ class BranchDoesNotExist(GiteaException):
|
||||
|
||||
|
||||
class BranchExists(GiteaException):
|
||||
RESPONSE_STATUS = 409
|
||||
# models/git/branch.go: return fmt.Sprintf("branch already exists [name: %s]", err.BranchName)
|
||||
# routers/api/v1/repo/branch.go: ctx.APIError(http.StatusConflict, "The branch already exists.")
|
||||
RESPONSE_MESSAGE_RE = [
|
||||
re.compile(r"branch already exists \[name: (?P<branch>.+)\]"),
|
||||
re.compile(r"The branch already exists\."),
|
||||
]
|
||||
|
||||
def __init__(self, response: GiteaHTTPResponse, owner: str, repo: str, branch: str):
|
||||
super().__init__(response)
|
||||
self.owner = owner
|
||||
@@ -48,22 +96,45 @@ class BranchExists(GiteaException):
|
||||
|
||||
|
||||
class ForkExists(GiteaException):
|
||||
def __init__(self, response: GiteaHTTPResponse, owner: str, repo: str):
|
||||
RESPONSE_STATUS = 409
|
||||
# services/repository/fork.go: return fmt.Sprintf("repository is already forked by user [uname: %s, repo path: %s, fork path: %s]", err.Uname, err.RepoName, err.ForkName)
|
||||
RESPONSE_MESSAGE_RE = [
|
||||
re.compile(r"repository is already forked by user \[uname: .+, repo path: (?P<owner>.+)/(?P<repo>.+), fork path: (?P<fork_owner>.+)/(?P<fork_repo>.+)\]"),
|
||||
]
|
||||
|
||||
def __init__(self, response: GiteaHTTPResponse, owner: str, repo: str, fork_owner: str, fork_repo: str):
|
||||
super().__init__(response)
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
|
||||
regex = re.compile(r".*fork path: (?P<owner>[^/]+)/(?P<repo>[^\]]+)\].*")
|
||||
match = regex.match(self.response.json()["message"])
|
||||
assert match is not None
|
||||
self.fork_owner = match.groupdict()["owner"]
|
||||
self.fork_repo = match.groupdict()["repo"]
|
||||
self.fork_owner = fork_owner
|
||||
self.fork_repo = fork_repo
|
||||
|
||||
def __str__(self):
|
||||
result = f"Repo '{self.owner}/{self.repo}' is already forked as '{self.fork_owner}/{self.fork_repo}'"
|
||||
return result
|
||||
|
||||
|
||||
class RepoExists(GiteaException):
|
||||
RESPONSE_STATUS = 409
|
||||
# models/repo/update.go: return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
|
||||
RESPONSE_MESSAGE_RE = [
|
||||
re.compile(r"^repository already exists \[uname: (?P<owner>.+), name: (?P<repo>.+)\]"),
|
||||
]
|
||||
|
||||
def __init__(self, response: GiteaHTTPResponse, owner: str, repo: str):
|
||||
super().__init__(response)
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
|
||||
def __str__(self):
|
||||
result = f"Repo '{self.owner}/{self.repo}' already exists"
|
||||
return result
|
||||
|
||||
|
||||
class InvalidSshPublicKey(oscerr.OscBaseError):
|
||||
def __str__(self):
|
||||
return "Invalid public ssh key"
|
||||
|
||||
|
||||
# gather all exceptions from this module that inherit from GiteaException
|
||||
EXCEPTION_CLASSES = [i for i in globals().values() if hasattr(i, "RESPONSE_MESSAGE_RE") and inspect.isclass(i) and issubclass(i, GiteaException)]
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import Optional
|
||||
from .connection import Connection
|
||||
from .connection import GiteaHTTPResponse
|
||||
from .exceptions import ForkExists
|
||||
from .exceptions import GiteaException
|
||||
|
||||
|
||||
class Fork:
|
||||
@@ -54,12 +53,9 @@ class Fork:
|
||||
url = conn.makeurl("repos", owner, repo, "forks")
|
||||
try:
|
||||
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
|
||||
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)
|
||||
raise fork_exists_exception from None
|
||||
except ForkExists as e:
|
||||
if not exist_ok:
|
||||
raise
|
||||
|
||||
from . import Repo # pylint: disable=import-outside-toplevel
|
||||
return Repo.get(conn, e.fork_owner, e.fork_repo)
|
||||
|
||||
Reference in New Issue
Block a user