Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion run_tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env python3
import sys
import unittest

if __name__ == "__main__":
loader = unittest.TestLoader()
suite = loader.discover("tests")

test_dir = sys.argv[1] if len(sys.argv) > 1 else "tests"

suite = loader.discover(test_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pathlib import Path

from dataclasses import dataclass
from enum import Enum
from git_flow_library import GitFlowLibrary

class CRStatus(str, Enum):
OPEN = "Open"
CLOSED = "Closed"
MERGED = "Merged"

def __str__(self):
return self.value

@dataclass
class CodeReview:
url: str
status: CRStatus
class GitFlowBranchStrategy:
def get_target_branch(self, directory: Path, source_branch: str) -> str:
try:
base = GitFlowLibrary.get_branch_base(source_branch, directory)
return base if base else GitFlowLibrary.get_develop_branch(directory)
except RuntimeError:
return "develop"
74 changes: 74 additions & 0 deletions src/sc/review/git_host_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2025 RDK Management
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from collections.abc import Iterable

from .models import CodeReview
from .exceptions import RemoteUrlNotFound
from .git_flow_branch_strategy import GitFlowBranchStrategy
from .git_instances import GitFactory, GitInstance
from .models import RepoInfo
from .review_config import GitHostConfig

class GitHostService:
def __init__(
self,
git_config: GitHostConfig | None = None,
factory: GitFactory | None = None,
branch_strategy: GitFlowBranchStrategy | None = None
):
self._git_config = git_config or GitHostConfig()
self._factory = factory or GitFactory()
self._branch_strategy = branch_strategy or GitFlowBranchStrategy()

def get_git_review_data(self, repo_info: RepoInfo) -> CodeReview | None:
git_instance = self._create_git_instance(repo_info.remote_url)
return git_instance.get_code_review(repo_info.repo_slug, repo_info.branch)

def get_create_cr_url(
self,
repo_info: RepoInfo,
) -> str:
git_instance = self._create_git_instance(repo_info.remote_url)
target_branch = self._branch_strategy.get_target_branch(
repo_info.directory, repo_info.branch)
return git_instance.get_create_cr_url(repo_info.repo_slug, repo_info.branch, target_branch)

def _create_git_instance(self, remote_url: str) -> GitInstance:
remote_pattern = _match_remote_pattern(
remote_url, self._git_config.get_patterns())
git_data = self._git_config.get(remote_pattern)
return self._factory.create(
git_data.provider,
token=git_data.token,
base_url=git_data.url
)

def _match_remote_pattern(remote_url: str, url_patterns: Iterable[str]) -> str:
"""Match the remote url to a pattern in the git instance config.

Args:
remote_url (str): The remote url of the git repository.
url_patterns (Iterable[str]): An iterable of patterns to check against.

Raises:
RemoteUrlNotFound: Raised when the remote url matches no patterns.

Returns:
str: The matched pattern.
"""
for pattern in url_patterns:
if pattern in remote_url:
return pattern
raise RemoteUrlNotFound(f"{remote_url} doesn't match any patterns! \n"
f"Remote patterns found: {', '.join(url_patterns)}")
11 changes: 4 additions & 7 deletions src/sc/review/git_instances/git_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import cache

from .instances import GithubInstance, GitlabInstance
from .git_instance import GitInstance
Expand All @@ -21,13 +22,9 @@ class GitFactory:
"gitlab": GitlabInstance
}

@classmethod
def types(cls) -> list[str]:
return list(cls._registry.keys())

@classmethod
def create(cls, name: str, token: str, base_url: str | None) -> GitInstance:
@cache
def create(self, name: str, token: str, base_url: str | None) -> GitInstance:
try:
return cls._registry[name.lower()](token=token, base_url=base_url)
return self._registry[name.lower()](token=token, base_url=base_url)
except KeyError:
raise ValueError(f"Provider name {name} doesn't match any VCS instance!")
7 changes: 4 additions & 3 deletions src/sc/review/git_instances/git_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from abc import ABC, abstractmethod

from ..code_review import CodeReview
from ..models import CodeReview

class GitInstance(ABC):
def __init__(self, token: str, base_url: str | None):
Expand All @@ -35,15 +35,16 @@ def validate_connection(self) -> bool:
pass

@abstractmethod
def get_code_review(self, repo: str, source_branch: str) -> CodeReview:
def get_code_review(self, repo: str, source_branch: str) -> CodeReview | None:
"""Get information about a branches code review.

Args:
repo (str): An identifier of the repo in the instance e.g "org/repo".
source_branch (str): The branch the code review is made from.

Returns:
CodeReview: dataclass with information about the code review.
CodeReview | None: dataclass with information about the code review or None
if not found.
"""
pass

Expand Down
3 changes: 2 additions & 1 deletion src/sc/review/git_instances/instances/github_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import requests

from sc.review.code_review import CRStatus, CodeReview
from sc.review.models import CRStatus, CodeReview
from ..git_instance import GitInstance

class GithubInstance(GitInstance):
Expand Down Expand Up @@ -77,6 +77,7 @@ def get_code_review(self, repo: str, source_branch: str) -> CodeReview | None:

if not prs:
return None

pr = prs[0]
# GitHub marks merged PRs as state="closed", merged=True
if pr.get("merged"):
Expand Down
8 changes: 4 additions & 4 deletions src/sc/review/git_instances/instances/gitlab_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import requests
import urllib.parse

from sc.review.code_review import CodeReview, CRStatus
from sc.review.models import CodeReview, CRStatus
from ..git_instance import GitInstance

Comment thread
BenjiMilan marked this conversation as resolved.
class GitlabInstance(GitInstance):
Expand Down Expand Up @@ -47,7 +47,7 @@ def validate_connection(self) -> bool:
raise ConnectionError(
f"Network connection to GitLab failed for {self.base_url}") from e

def get_code_review(self, repo: str, source_branch: str) -> CodeReview:
def get_code_review(self, repo: str, source_branch: str) -> CodeReview | None:
"""Get information about a code review.

Args:
Expand Down Expand Up @@ -97,8 +97,8 @@ def get_create_cr_url(
self,
repo: str,
source_branch: str,
target_branch: str="develop"
):
target_branch: str = "develop"
) -> str:
params = {
"merge_request[source_branch]": source_branch,
"merge_request[target_branch]": target_branch,
Expand Down
160 changes: 0 additions & 160 deletions src/sc/review/main.py

This file was deleted.

Loading
Loading