Skip to content

Conversation

@BenjiMilan
Copy link
Contributor

Sc review

@BenjiMilan BenjiMilan requested a review from TB-1993 November 18, 2025 08:54
@BenjiMilan BenjiMilan self-assigned this Nov 18, 2025
Copilot AI review requested due to automatic review settings November 18, 2025 08:54
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 8 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: 56ca670

Report detail: gist'

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new review feature (BWDO-520) that integrates source control review functionality with ticketing systems (Jira and Redmine) and Git hosting providers (GitHub and GitLab). The feature allows users to add commit/PR information to tickets and manage VCS and ticketing instances through a CLI.

Key Changes:

  • Added CLI commands for review operations including review, add_git_instance, and add_ticketing_instance
  • Implemented ticketing system integrations for Jira and Redmine with CRUD operations
  • Implemented Git hosting provider integrations for GitHub and GitLab with PR management
  • Created configuration management for ticketing and Git instances

Reviewed Changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 29 comments.

Show a summary per file
File Description
src/sc/review_cli.py New CLI module exposing review commands
src/sc/review/ticketing_instances/ticketing_instance.py Abstract base class for ticketing system implementations
src/sc/review/ticketing_instances/ticket_instance_factory.py Factory for creating ticketing instance objects
src/sc/review/ticketing_instances/ticket.py Data class representing a ticket
src/sc/review/ticketing_instances/redmine_instance.py Redmine ticketing system implementation
src/sc/review/ticketing_instances/jira_instance.py Jira ticketing system implementation
src/sc/review/review_config.py Configuration management for review feature
src/sc/review/review.py Core review logic for processing repos and tickets
src/sc/review/main.py Entry points for review commands with user interaction
src/sc/review/git_instances/pull_request.py Data class for pull request representation
src/sc/review/git_instances/gitlab_instance.py GitLab API integration
src/sc/review/git_instances/github_instance.py GitHub API integration
src/sc/review/git_instances/git_instance.py Abstract base class for Git hosting providers
src/sc/review/git_instances/git_factory.py Factory for creating Git instance objects
src/sc/review/exceptions.py Custom exceptions for review operations
src/sc/cli.py Updated to import and register review CLI commands
setup.py Added dependencies for jira and python-redmine libraries
Comments suppressed due to low confidence (4)

src/sc/review/ticketing_instances/redmine_instance.py:200

                '{url}/issues/{ticket_number}'.format(url=self._url),

src/sc/review/ticketing_instances/redmine_instance.py:40

  • Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.
    def validate_connection(self) -> bool:

src/sc/review/ticketing_instances/redmine_instance.py:76

            raise TicketingInstanceUnreachable(
                self.url,
                additional_information=''.join(
                    arg for arg in exception.args)) from exception

src/sc/review/ticketing_instances/redmine_instance.py:48

  • Instantiating an exception, but not raising it, has no effect.
            ConnectionError("Invalid Redmine API key or insufficient permssions.")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"""Creates a ticket on the Jira instance
Args:
project_key (str): The projects key on th Jira instance
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in docstring: "th Jira" should be "the Jira".

Suggested change
project_key (str): The projects key on th Jira instance
project_key (str): The project's key on the Jira instance

Copilot uses AI. Check for mistakes.
logger.error(f"Failed to connect! {e}")
sys.exit(1)

logger.info("Connection succesful!")
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in log message: "succesful" should be "successful".

Suggested change
logger.info("Connection succesful!")
logger.info("Connection successful!")

Copilot uses AI. Check for mistakes.
output_location: str = None,
obfuscate: bool = False):
filepath = abspath(file_location) if exists(file_location) else None
if filepath is None and self._downloader:
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _get_file method references self._downloader on line 157, but this attribute is never initialized in the __init__ method or anywhere else in the class. This will cause an AttributeError when the method is called.

Copilot uses AI. Check for mistakes.

if provider == "github":
url = "http://api.github.com"
logger.info("Enter a pattern for to identify Git from remote url: ")
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in log message: "for to identify" should be "to identify" (remove the extra "for").

Suggested change
logger.info("Enter a pattern for to identify Git from remote url: ")
logger.info("Enter a pattern to identify Git from remote url: ")

Copilot uses AI. Check for mistakes.
f"Author: [{data.commit_author}]",
f"Date: [{data.commit_date}]",
"",
f"{data.commit_message}"
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma after line 349, causing the string "</pre>" on line 350 to be concatenated with the previous line instead of being a separate element in the tuple.

Suggested change
f"{data.commit_message}"
f"{data.commit_message}",

Copilot uses AI. Check for mistakes.
def create_ticket(self) -> Ticket:
"""Abstract Method:
Create a ticket on the ticketing instance.
Read it's contents return the new ticket object
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in docstring: "it's contents" should be "its contents" (possessive form, no apostrophe).

Suggested change
Read it's contents return the new ticket object
Read its contents return the new ticket object

Copilot uses AI. Check for mistakes.
Comment on lines 193 to 195
url=self._instance.url, ticket_number=self.ticket_id)
try:
self._instance.issue.delete(self.ticket_id)
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delete_ticket method uses self.ticket_id instead of the ticket_id parameter passed to the function. This will cause an AttributeError since self.ticket_id is not defined on the instance. Use the ticket_id parameter instead.

Suggested change
url=self._instance.url, ticket_number=self.ticket_id)
try:
self._instance.issue.delete(self.ticket_id)
url=self._instance.url, ticket_number=ticket_id)
try:
self._instance.issue.delete(ticket_id)

Copilot uses AI. Check for mistakes.
raise TicketNotFound(issue_url) from exception
except ForbiddenError as exception:
raise PermissionsError(
'{url}/issues/{ticket_number}'.format(url=self._url),
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent error message formatting: line 200 uses self._url without the ticket_number placeholder, unlike line 193. This should match the pattern used in line 192-193 for consistency.

Suggested change
'{url}/issues/{ticket_number}'.format(url=self._url),
issue_url,

Copilot uses AI. Check for mistakes.
Comment on lines 97 to 109
def create_ticket(self, project_key: str, issue_type: str, title: str,
**kwargs):
"""Creates a ticket on the Jira instance
Args:
project_key (str): The projects key on th Jira instance
issue_type (str): The type of issue to raise
titlr (str): Title of the ticket to raise
"""
project_dict = {'project': {'key': project_key}}
kwargs.update(project_dict)
kwargs.update({'issuetype': issue_type, 'summary': title})
new_issue_id = self._instance.create_issue(fields=kwargs).key
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method requires 4 positional arguments, whereas overridden TicketingInstance.create_ticket requires 1.

Suggested change
def create_ticket(self, project_key: str, issue_type: str, title: str,
**kwargs):
"""Creates a ticket on the Jira instance
Args:
project_key (str): The projects key on th Jira instance
issue_type (str): The type of issue to raise
titlr (str): Title of the ticket to raise
"""
project_dict = {'project': {'key': project_key}}
kwargs.update(project_dict)
kwargs.update({'issuetype': issue_type, 'summary': title})
new_issue_id = self._instance.create_issue(fields=kwargs).key
def create_ticket(self, ticket_data):
"""Creates a ticket on the Jira instance
Args:
ticket_data (dict): Dictionary containing ticket fields. Must include
'project_key', 'issue_type', and 'title'.
"""
project_key = ticket_data.get('project_key')
issue_type = ticket_data.get('issue_type')
title = ticket_data.get('title')
# Remove these keys from ticket_data to avoid duplication
fields = {k: v for k, v in ticket_data.items() if k not in ('project_key', 'issue_type', 'title')}
project_dict = {'project': {'key': project_key}}
fields.update(project_dict)
fields.update({'issuetype': issue_type, 'summary': title})
new_issue_id = self._instance.create_issue(fields=fields).key

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 67
def create_ticket(self, project_name: str, title: str, **kwargs) -> Ticket:
"""Create a ticket on the redmine instance
Args:
project_name (str): The project to create the ticket under
title (str): The title of the ticket
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method requires 3 positional arguments, whereas overridden TicketingInstance.create_ticket requires 1.

Suggested change
def create_ticket(self, project_name: str, title: str, **kwargs) -> Ticket:
"""Create a ticket on the redmine instance
Args:
project_name (str): The project to create the ticket under
title (str): The title of the ticket
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
def create_ticket(self, title: str, **kwargs) -> Ticket:
"""Create a ticket on the redmine instance
Args:
title (str): The title of the ticket
project_name (str, in kwargs): The project to create the ticket under
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
project_name = kwargs.pop('project_name', None)
if not project_name:
raise ValueError("Missing required argument: 'project_name'")

Copilot uses AI. Check for mistakes.
@rdkcmf-jenkins
Copy link
Contributor

b'## WARNING: A Blackduck scan failure has been waived

A prior failure has been upvoted

  • Upvote reason: ok

  • Commit: 56ca670
    '

@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from 56ca670 to fb132d5 Compare November 18, 2025 10:25
Copilot AI review requested due to automatic review settings November 18, 2025 13:52
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from fb132d5 to 87201bf Compare November 18, 2025 13:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (2)

src/sc/review/ticketing_instances/redmine_instance.py:200

                '{url}/issues/{ticket_number}'.format(url=self._url),

src/sc/review/ticketing_instances/redmine_instance.py:48

  • Instantiating an exception, but not raising it, has no effect.
            ConnectionError("Invalid Redmine API key or insufficient permssions.")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

comment_message (str): The message to add as a comment
ticket_id (str): The ticket number to add the comment to.
"""
ticket = self.update_ticket(ticket_id,notes=self._convert_html_colours(comment_message))
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in function call. notes=self._convert_html_colours(comment_message) should have a space: notes=self._convert_html_colours(comment_message). While Python allows this, PEP 8 recommends spaces around the = in keyword arguments only when not following a comma, but there should be a space after the comma before notes.

Suggested change
ticket = self.update_ticket(ticket_id,notes=self._convert_html_colours(comment_message))
ticket = self.update_ticket(ticket_id, notes=self._convert_html_colours(comment_message))

Copilot uses AI. Check for mistakes.
raise TicketNotFound(issue_url) from exception
except ForbiddenError as exception:
raise PermissionsError(
'{url}/issues/{ticket_number}'.format(url=self._url),
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete URL formatting. Line 200 references {ticket_number} in the format string but doesn't provide this variable - it should use ticket_id parameter. This will cause a KeyError.

Suggested change
'{url}/issues/{ticket_number}'.format(url=self._url),
'{url}/issues/{ticket_number}'.format(url=self._url, ticket_number=ticket_id),

Copilot uses AI. Check for mistakes.
# If the user does not want to use SSL verification disable the warnings about it being insecure.
if self._default_jira_setup.get('verify') is False:
disable_warnings(InsecureRequestWarning)
# 9.0.0. is an arbitrary version that hasn't been release yet
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment. "release" should be "released".

Suggested change
# 9.0.0. is an arbitrary version that hasn't been release yet
# 9.0.0. is an arbitrary version that hasn't been released yet

Copilot uses AI. Check for mistakes.
r = requests.get(url, headers=self._headers(), params=params, timeout=10)
r.raise_for_status()
prs = r.json()
print(r.json())
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. Line 37 contains print(r.json()) which should be removed or replaced with proper logging.

Suggested change
print(r.json())

Copilot uses AI. Check for mistakes.
def get_ticket_host_data(self, identifier: str) -> TicketHostCfg:
data = self._ticket_config.get_config().get(identifier)
if not data:
raise KeyError(f"Git instance config doesn't contain entry for {identifier}")
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect error message in KeyError. The message says "Git instance config doesn't contain entry for {identifier}" but this method is for ticket host configuration, not git instance configuration. Should say "Ticket host config doesn't contain entry for {identifier}".

Suggested change
raise KeyError(f"Git instance config doesn't contain entry for {identifier}")
raise KeyError(f"Ticket host config doesn't contain entry for {identifier}")

Copilot uses AI. Check for mistakes.
f"Date: [{data.commit_date}]",
"",
f"{data.commit_message}"
"</pre>"
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing comma in tuple. Line 351 should end with a comma to maintain tuple structure consistency. The closing </pre> should be "</pre>" (with comma after).

Suggested change
"</pre>"
"</pre>",

Copilot uses AI. Check for mistakes.
def engine(self) -> str:
return 'redmine'

def validate_connection(self) -> bool:
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.

Copilot uses AI. Check for mistakes.
Comment on lines 75 to 76
additional_information=''.join(
arg for arg in exception.args)) from exception
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyword argument 'additional_information' is not a supported parameter name of TicketingInstanceUnreachable.init.

Suggested change
additional_information=''.join(
arg for arg in exception.args)) from exception
''.join(arg for arg in exception.args)
) from exception

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 29
def get_pull_request(self, repo: str, source_branch: str) -> PullRequest:
pass

@abstractmethod
def get_create_pr_url(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to review to PRs at code_reviews, for a more instance agnostic approach. Since gitlab refers them as merge_requests and PRs are a github thing.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have this file and the git_instances folder. Since this should just import all instance types from the folder and is the only to be imported from the files above. Then have an init.py file in the git_instance folder to only expose the implemented instances and not the abstract instance.

Comment on lines +40 to +56
logger.info(
"E.G. github.com for all github instances or "
"github.com/org for a particular organisation")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should revisit this at a later date. I don't think this is a particularly intuitive way to do it, but it will work fine for now.

instance = GitFactory.create(provider, api_key, url)

try:
instance.validate_connection()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we just do this as part of the create from GitFactory?


logger.info("Connection validated!")

git_cfg = GitInstanceCfg(url=url, token=api_key, provider=provider)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not have this come from the instance, since we just passed all this data in there, so it should know it's configuration.

Comment on lines 47 to 50
identifier, ticket_num = self._match_branch(
branch_name,
ticket_host_ids
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not stick the self._config.get_ticket_host_ids() into the _match_branch function and knock out the ticket_host_ids arg.

ticketing_instance, ticketing_cfg = self._load_ticketing(identifier)

ticket_id = f"{ticketing_cfg.project_prefix or ''}{ticket_num}"
ticket = ticketing_instance.read_ticket(ticket_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be wrapped in a try/except to catch when it throws errors and handle them nicely.

logger.warning(e)
identifier, ticket_num = None, None

if identifier and ticket_num:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid this if statement if you just used the try/except above as the if/else

manifest_repo, manifest_git)
comments.append(comment_data)

print(self._generate_combined_terminal_comment(comments))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it still print out in colour in the terminal?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this out of the ticketing_instances dir

Copilot AI review requested due to automatic review settings November 24, 2025 14:46
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 4 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: fef1d98

Report detail: gist'

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return True

except (AuthError, ForbiddenError) as e:
raise ConnectionError("Invalid Redmine API key or insufficient permssions.")
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'permssions' to 'permissions'.

Suggested change
raise ConnectionError("Invalid Redmine API key or insufficient permssions.")
raise ConnectionError("Invalid Redmine API key or insufficient permissions.")

Copilot uses AI. Check for mistakes.
Args:
project_key (str): The project's key on the Jira instance
issue_type (str): The type of issue to raise
titlr (str): Title of the ticket to raise
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'titlr' to 'title'.

Suggested change
titlr (str): Title of the ticket to raise
title (str): Title of the ticket to raise

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,2 @@
from .git_factory import GitFactory
from . ticket_instance_factory import TicketingInstanceFactory No newline at end of file
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a space after the dot in the import statement. It should be from .ticket_instance_factory without the space.

Suggested change
from . ticket_instance_factory import TicketingInstanceFactory
from .ticket_instance_factory import TicketingInstanceFactory

Copilot uses AI. Check for mistakes.
print("")

if provider == "github":
url = "http://api.github.com"
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub API URL should use HTTPS protocol. This should be https://api.github.com to match the default in GithubInstance (line 8 of github_instance.py) and to ensure secure communication.

Suggested change
url = "http://api.github.com"
url = "https://api.github.com"

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 69


Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing sys.exit(1) or return after error message. If an invalid provider is entered, the code continues execution and will fail with NameError on line 60 when trying to use undefined variables url and pattern.

Suggested change
sys.exit(1)

Copilot uses AI. Check for mistakes.
Comment on lines 62 to 72
def create_ticket(self, project_name: str, title: str, **kwargs) -> Ticket:
"""Create a ticket on the redmine instance
Args:
project_name (str): The project to create the ticket under
title (str): The title of the ticket
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method requires 3 positional arguments, whereas overridden TicketingInstance.create_ticket requires 1.

Suggested change
def create_ticket(self, project_name: str, title: str, **kwargs) -> Ticket:
"""Create a ticket on the redmine instance
Args:
project_name (str): The project to create the ticket under
title (str): The title of the ticket
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
def create_ticket(self, ticket_data) -> Ticket:
"""Create a ticket on the redmine instance
Args:
ticket_data (dict): Dictionary containing ticket fields, must include 'project_name' and 'title'
Raises:
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
project_name = ticket_data.get('project_name')
title = ticket_data.get('title')
# Remove project_name and title from ticket_data to avoid passing them twice
kwargs = {k: v for k, v in ticket_data.items() if k not in ('project_name', 'title')}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
from abc import ABC, abstractmethod
from os.path import abspath, exists
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'abspath' is not used.
Import of 'exists' is not used.

Suggested change
from os.path import abspath, exists

Copilot uses AI. Check for mistakes.
@mhughesacn
Copy link

Hi @BenjiMilan : Please add headers to all new code files (except very small 1 or 2 liners).

Copilot AI review requested due to automatic review settings December 9, 2025 11:07
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from 9b7241a to 1a387bf Compare December 9, 2025 11:07
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from 1a387bf to cfb26b2 Compare December 9, 2025 11:08
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 25 changed files in this pull request and generated 20 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

try:
comment = self._convert_from_html(comment_message)
ticket = self._instance.add_comment(ticket_id, comment=comment)
ticket = self._instance.up
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line appears to be incomplete. The code attempts to call self._instance.up which is not a valid method or attribute access. This will cause an AttributeError at runtime.

Suggested change
ticket = self._instance.up
ticket = self.read_ticket(ticket_id)

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 151
class RedmineInstance(TicketingInstance):
"""
A class to handle operations on Redmine tickets.
"""

def __init__(
self,
instance: Redmine
):
self._instance = instance

@property
def engine(self) -> str:
return 'redmine'

def validate_connection(self) -> bool:
"""Check if the Redmine instance and API key are valid.
Raises:
ConnectionError: If the connection is invalid.
Returns:
bool: True if connection is valid.
"""
try:
# Minimal authenticated request
self._instance.auth() # redminelib verifies credentials
return True

except (AuthError, ForbiddenError) as e:
raise ConnectionError("Invalid Redmine API key or insufficient permssions.")

except BaseRedmineError as e:
raise ConnectionError(f"Redmine API error: {e}")

except RequestException as e:
raise ConnectionError("Failed to reach Redmine server.") from e

def read_ticket(self, ticket_id: str) -> Ticket:
"""Read the information from a ticket and put it's contents in this object contents dict
Args:
ticket_id (str): The ticket number to read.
Raises:
TicketNotFound: Ticket not found on the redmine instance
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
ticket_url = self.url + '/issues/' + ticket_id
try:
issue = self._instance.issue.get(ticket_id, include=['journals'])
except ResourceNotFoundError as exception:
raise TicketNotFound(ticket_url) from exception
except (AuthError, ForbiddenError) as exception:
raise PermissionsError(
ticket_url,
'Please contact the dev-support team') from exception
except SSLError as exception:
raise TicketingInstanceUnreachable(
ticket_url,
additional_info=''.join(str(arg) for arg in exception.args))
issue_contents = dict((k, v) for k, v in list(issue))
author = issue_contents['author'].get('name')
try:
assignee = issue_contents['assigned_to'].get('name')
except KeyError:
assignee = None
comments = issue_contents.get('journals')
status = issue_contents['status'].get('name')
try:
target_version = issue_contents['fixed_version'].get('name')
except KeyError:
target_version = None
title = issue_contents.get('subject')
ticket = Ticket(ticket_url,
author=author,
assignee=assignee,
comments=comments,
contents=issue_contents,
id=ticket_id,
status=status,
title=title,
url=ticket_url,
target_version=target_version)
return ticket

def update_ticket(self, ticket_id: str, **kwargs):
"""Writes the changed fields from the kwargs, back to the ticket
Raises:
TicketNotFound: Ticket not found on the redmine instance
PermissionsError: User does not have permission to access the ticket on the instances
TicketingInstanceUnreachable: The redmine instance is unreachable
"""
issue_url = f'{self.url}/issues/{ticket_id}'
try:
self._instance.issue.update(ticket_id, **kwargs)
except ResourceNotFoundError as exception:
raise TicketNotFound(issue_url) from exception
except ForbiddenError as exception:
raise PermissionsError(
issue_url, 'Please contact the dev-support team') from exception
except SSLError as exception:
raise TicketingInstanceUnreachable(
issue_url,
additional_info=''.join(str(arg) for arg in exception.args))
ticket = self.read_ticket(ticket_id)
return ticket

def add_comment_to_ticket(self, ticket_id: str, comment_message: str) -> None:
"""Add a comment to a ticket on the redmine instance
Args:
comment_message (str): The message to add as a comment
ticket_id (str): The ticket number to add the comment to.
"""
ticket = self.update_ticket(
ticket_id, notes=self._convert_html_colours(comment_message))
return ticket

def _convert_html_colours(self, string: str) -> str:
"""Convert a html colour tags to redmine formatted colour tags.
Args:
string (str): html formatted string.
Returns:
str: Input string with html colour tags converted to redmine colour tags.
"""
string = string.replace('<p style="', r'%{')
string = string.replace('">', '}')
string = string.replace('</p>', r'%')
return string
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RedmineInstance class doesn't implement the abstract methods create_ticket and delete_ticket required by the TicketingInstance base class. This will cause errors if these methods are called.

Copilot uses AI. Check for mistakes.
git_instance = self._create_git_instance(Repo(self.top_dir).remote().url)
comment_data = self._create_comment_data(Repo(self.top_dir), git_instance)
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling: Repo(self.top_dir).remote().url assumes a default remote exists. If no remote is configured or the default remote is not named 'origin', this will raise an exception. Consider using Repo(self.top_dir).remotes[0].url with proper validation or specifying the remote name explicitly.

Suggested change
git_instance = self._create_git_instance(Repo(self.top_dir).remote().url)
comment_data = self._create_comment_data(Repo(self.top_dir), git_instance)
repo = Repo(self.top_dir)
# Try to get the remote URL safely
if not repo.remotes:
raise RemoteUrlNotFound(f"No remotes found for repository at {self.top_dir}")
# Prefer 'origin' if it exists, else use the first remote
remote = repo.remotes.origin if 'origin' in repo.remotes else repo.remotes[0]
git_instance = self._create_git_instance(remote.url)
comment_data = self._create_comment_data(repo, git_instance)

Copilot uses AI. Check for mistakes.
Comment on lines 44 to 43
instance: JIRA
):
self._instance = instance
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JiraInstance class is missing the url property, but it's being used in methods (e.g., line 93, 122). This will cause an AttributeError at runtime.

Suggested change
instance: JIRA
):
self._instance = instance
instance: JIRA,
url: str
):
self._instance = instance
self.url = url

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +289
def c(code, text):
return f"\033[{code}m{text}\033[0m"
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The function name c is too short and unclear. Consider renaming it to something more descriptive like colorize or add_ansi_color to better indicate its purpose of adding ANSI color codes.

Copilot uses AI. Check for mistakes.
raise ConnectionError("Failed to reach Redmine server.") from e

def read_ticket(self, ticket_id: str) -> Ticket:
"""Read the information from a ticket and put it's contents in this object contents dict
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring states "it's contents" but should be "its contents" (no apostrophe for possessive).

Suggested change
"""Read the information from a ticket and put it's contents in this object contents dict
"""Read the information from a ticket and put its contents in this object contents dict

Copilot uses AI. Check for mistakes.
"""
try:
comment = self._convert_from_html(comment_message)
ticket = self._instance.add_comment(ticket_id, comment=comment)
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'ticket' is unnecessary as it is redefined before this value is used.
This assignment to 'ticket' is unnecessary as it is redefined before this value is used.

Suggested change
ticket = self._instance.add_comment(ticket_id, comment=comment)
self._instance.add_comment(ticket_id, comment=comment)

Copilot uses AI. Check for mistakes.
# See the License for the specific language governing permissions and
# limitations under the License.

from json.decoder import JSONDecodeError
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'JSONDecodeError' is not used.

Suggested change
from json.decoder import JSONDecodeError

Copilot uses AI. Check for mistakes.

from json.decoder import JSONDecodeError
from requests import post
from requests.exceptions import InvalidURL, RequestException
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'InvalidURL' is not used.

Suggested change
from requests.exceptions import InvalidURL, RequestException
from requests.exceptions import RequestException

Copilot uses AI. Check for mistakes.
from jira import JIRA
from jira.exceptions import JIRAError

from ..exceptions import PermissionsError, TicketingInstanceUnreachable, TicketNotFound
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'TicketingInstanceUnreachable' is not used.

Suggested change
from ..exceptions import PermissionsError, TicketingInstanceUnreachable, TicketNotFound
from ..exceptions import PermissionsError, TicketNotFound

Copilot uses AI. Check for mistakes.
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 2 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: 2f97745

Report detail: gist'

Copilot AI review requested due to automatic review settings December 17, 2025 11:12
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from 2f97745 to 3923af5 Compare December 17, 2025 11:12
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 2 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: 3923af5

Report detail: gist'

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated 19 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

print("")

if provider == "github":
url = "http://api.github.com"
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GitHub API URL should use HTTPS instead of HTTP for security. The correct URL is "https://api.github.com".

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,2 @@
from .git_factory import GitFactory
from . ticket_instance_factory import TicketingInstanceFactory No newline at end of file
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect spacing in import statement. There should be no space between the dot and "ticket_instance_factory".

Copilot uses AI. Check for mistakes.
Comment on lines 45 to 50
def get_code_review(self, repo: str, source_branch: str) -> CodeReview:
safe_repo = urllib.parse.quote(repo, safe='')
url = f"{self.base_url}/api/v4/projects/{safe_repo}/merge_requests"
params = {"state": "all", "source_branch": source_branch}
r = requests.get(url, headers=self._headers(), params=params, timeout=10)
r.raise_for_status()
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_code_review method doesn't handle potential network errors. If requests.get raises an exception (timeout, connection error, HTTP error), it will propagate uncaught. Consider adding try-except blocks similar to the validate_connection method to provide meaningful error messages.

Copilot uses AI. Check for mistakes.

input_id = input("> ")
while input_id not in ticket_conf.keys():
logger.info("Prefix {input_id} not found in instances.")
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing f-string prefix. The string contains {input_id} but isn't an f-string, so it will print the literal text "Prefix {input_id} not found in instances." instead of interpolating the variable.

Copilot uses AI. Check for mistakes.
Comment on lines 46 to 51
def get_code_review(self, repo: str, source_branch: str) -> CodeReview | None:
url = f"{self.base_url}/repos/{repo}/pulls"
owner = repo.split("/")[0]
params = {"state": "all", "head": f"{owner}:{source_branch}"}
r = requests.get(url, headers=self._headers(), params=params, timeout=10)
r.raise_for_status()
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get_code_review method doesn't handle potential network errors. If requests.get raises an exception (timeout, connection error, HTTP error), it will propagate uncaught. Consider adding try-except blocks similar to the validate_connection method to provide meaningful error messages.

Copilot uses AI. Check for mistakes.
ticketing_instance.add_comment_to_ticket(ticket_id, ticket_comment)

def run_repo_command(self):
manifest_repo = Repo(self.top_dir / '.repo' / 'manifests')
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type inconsistency. The init method accepts top_dir as a str, but in run_repo_command (line 81), it's used with Path division operator (/), which requires top_dir to be a Path object. This will raise a TypeError. Either convert top_dir to Path in init (self.top_dir = Path(top_dir)) or use Path(self.top_dir) when performing path operations.

Copilot uses AI. Check for mistakes.
logger.info(f"Ticket URL: [{ticket.url if ticket else ''}]")
logger.info("Ticket info: ")

manifest = ScManifest.from_repo_root(self.top_dir / '.repo')
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type inconsistency. The init method accepts top_dir as a str, but this line uses Path division operator (/), which requires top_dir to be a Path object. This will raise a TypeError. Either convert top_dir to Path in init (self.top_dir = Path(top_dir)) or use Path(self.top_dir) when performing path operations.

Copilot uses AI. Check for mistakes.
# 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.
import urllib3
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'urllib3' is not used.

Copilot uses AI. Check for mistakes.
from ..ticketing_instances import JiraInstance, RedmineInstance
from ..core.ticketing_instance import TicketingInstance

from jira import JIRA
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'JIRA' is not used.

Copilot uses AI. Check for mistakes.
from ..core.ticketing_instance import TicketingInstance

from jira import JIRA
from redminelib import Redmine
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Redmine' is not used.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings January 5, 2026 15:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return True

except (AuthError, ForbiddenError) as e:
raise ConnectionError("Invalid Redmine API key or insufficient permssions.")
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'permssions' to 'permissions'.

Suggested change
raise ConnectionError("Invalid Redmine API key or insufficient permssions.")
raise ConnectionError("Invalid Redmine API key or insufficient permissions.")

Copilot uses AI. Check for mistakes.
Comment on lines 236 to 243
def _create_ticketing_instance(self, cfg: TicketHostCfg) -> TicketingInstance:
inst = TicketingInstanceFactory.create(
provider=cfg.provider,
url=cfg.url,
token=cfg.api_key,
auth_type=cfg.auth_type,
username=cfg.username,
cert=cfg.cert
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter name cfg is ambiguous. It should be renamed to ticket_host_cfg or config for clarity.

Suggested change
def _create_ticketing_instance(self, cfg: TicketHostCfg) -> TicketingInstance:
inst = TicketingInstanceFactory.create(
provider=cfg.provider,
url=cfg.url,
token=cfg.api_key,
auth_type=cfg.auth_type,
username=cfg.username,
cert=cfg.cert
def _create_ticketing_instance(self, ticket_host_cfg: TicketHostCfg) -> TicketingInstance:
inst = TicketingInstanceFactory.create(
provider=ticket_host_cfg.provider,
url=ticket_host_cfg.url,
token=ticket_host_cfg.api_key,
auth_type=ticket_host_cfg.auth_type,
username=ticket_host_cfg.username,
cert=ticket_host_cfg.cert

Copilot uses AI. Check for mistakes.

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

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrase "about about" contains a duplicate word. Should be "about a branch's code review."

Suggested change
"""Get information about about a branches code review.
"""Get information about a branch's code review.

Copilot uses AI. Check for mistakes.
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from 66fb6e8 to 8737e84 Compare January 5, 2026 15:56
Copilot AI review requested due to automatic review settings January 6, 2026 13:44
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 4 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: b6a9b80

Report detail: gist'

@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 4 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: b6a9b80

Report detail: gist'

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 26 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


except (AuthError, ForbiddenError) as e:
raise ConnectionError(
"Invalid Redmine API key or insufficient permssions for "
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "permssions" should be "permissions".

Suggested change
"Invalid Redmine API key or insufficient permssions for "
"Invalid Redmine API key or insufficient permissions for "

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +37
verify_ssl: bool = False
):
if not verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSL verification is disabled by default (verify_ssl=False). This is a security risk as it makes the connection vulnerable to man-in-the-middle attacks. Consider making SSL verification enabled by default and requiring explicit opt-out, or at minimum add a prominent warning in the documentation.

Suggested change
verify_ssl: bool = False
):
if not verify_ssl:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
verify_ssl: bool = True
):

Copilot uses AI. Check for mistakes.

@cli.command()
def review():
"""Add commit/PR information to your ticket."""
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring states "Add commit/PR information to your ticket", but this is the same generic description used for all three commands (review, add_git_instance, add_ticketing_instance). The add_git_instance and add_ticketing_instance docstrings correctly describe their purpose. Consider updating the review command's docstring to something more specific like "Add commit and code review information to your ticket".

Suggested change
"""Add commit/PR information to your ticket."""
"""Add commit and code review information to your ticket."""

Copilot uses AI. Check for mistakes.
Comment on lines 53 to 61
except requests.Timeout as e:
raise RuntimeError("Gitlab request timed out") from e
except requests.HTTPError as e:
raise RuntimeError(
f"Gitlab API error {r.status_code}: {r.text}"
) from e
except ValueError as e: # JSON decode error
raise RuntimeError("Invalid JSON from Gitlab API") from e
except requests.RequestException as e:
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handling catches specific requests.Timeout and requests.HTTPError exceptions, but these don't exist in the requests library. The correct exception classes are requests.exceptions.Timeout and requests.exceptions.HTTPError. This will cause an AttributeError at runtime when these exceptions are raised.

Suggested change
except requests.Timeout as e:
raise RuntimeError("Gitlab request timed out") from e
except requests.HTTPError as e:
raise RuntimeError(
f"Gitlab API error {r.status_code}: {r.text}"
) from e
except ValueError as e: # JSON decode error
raise RuntimeError("Invalid JSON from Gitlab API") from e
except requests.RequestException as e:
except requests.exceptions.Timeout as e:
raise RuntimeError("Gitlab request timed out") from e
except requests.exceptions.HTTPError as e:
raise RuntimeError(
f"Gitlab API error {r.status_code}: {r.text}"
) from e
except ValueError as e: # JSON decode error
raise RuntimeError("Invalid JSON from Gitlab API") from e
except requests.exceptions.RequestException as e:

Copilot uses AI. Check for mistakes.
Comment on lines 56 to 57
raise RuntimeError(
f"Gitlab API error {r.status_code}: {r.text}"
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable r is referenced in the error handling blocks (lines 57, 59) but may not be defined if the exception occurs during the requests.get() call itself (line 50). This will cause a NameError. Move the status_code and text access into a try-except block or check if r is defined before using it.

Suggested change
raise RuntimeError(
f"Gitlab API error {r.status_code}: {r.text}"
status_code = e.response.status_code if e.response is not None else "unknown"
response_text = e.response.text if e.response is not None else ""
raise RuntimeError(
f"Gitlab API error {status_code}: {response_text}"

Copilot uses AI. Check for mistakes.
Comment on lines +192 to +193
if slug.endswith(".git"):
slug = slug[:-4]
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code extracts slug from remote URLs and removes ".git" suffix if present, but doesn't handle the case where ".git" appears elsewhere in the path (e.g., "repo.git.backup.git"). While unlikely, using slug.removesuffix('.git') (Python 3.9+) or slug[:-4] if slug.endswith('.git') else slug would be more robust and only remove the suffix if it's at the end.

Suggested change
if slug.endswith(".git"):
slug = slug[:-4]
slug = slug.removesuffix(".git")

Copilot uses AI. Check for mistakes.

def _create_comment_data(self, repo: Repo, git_instance: GitInstance) -> CommentData:
branch_name = repo.active_branch.name
repo_slug = self._get_repo_slug(repo.remotes[0].url)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses repo.remotes[0].url without checking if the repository has any remotes. If the repository has no remotes configured, this will raise an IndexError. Consider adding a check for empty remotes or handling the IndexError.

Copilot uses AI. Check for mistakes.
raise ConnectionError("GitHub API request timed out.") from e
except requests.exceptions.HTTPError as e:
status = e.response.status_code
if status == 400:
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling checks for status code 400 to indicate an invalid token, but GitHub typically returns 401 for authentication failures, not 400. Status code 400 is for bad requests (malformed request syntax). Consider checking for status codes 401 (Unauthorized) and 403 (Forbidden) like the GitLab implementation does.

Suggested change
if status == 400:
if status in (401, 403):

Copilot uses AI. Check for mistakes.
Comment on lines +138 to +142
for identifier in host_identifiers:
# Matches the identifier, followed by - or _, followed by a number
if m := re.search(fr'{identifier}[-_]?(\d+)', branch_name):
ticket_num = m.group(1)
return identifier, ticket_num
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code iterates through all host identifiers and performs a regex search for each one. For repositories with many configured ticketing instances, this could be inefficient. Consider using a single compiled regex pattern that captures all identifiers in one pass, or compile the regex patterns once rather than on each call.

Suggested change
for identifier in host_identifiers:
# Matches the identifier, followed by - or _, followed by a number
if m := re.search(fr'{identifier}[-_]?(\d+)', branch_name):
ticket_num = m.group(1)
return identifier, ticket_num
# Build a single regex that matches any identifier followed by - or _, then a number.
combined_pattern = re.compile(
rf"({'|'.join(re.escape(ident) for ident in host_identifiers)})[-_]?(\d+)"
)
m = combined_pattern.search(branch_name)
if m:
identifier = m.group(1)
ticket_num = m.group(2)
return identifier, ticket_num

Copilot uses AI. Check for mistakes.
str: The matched pattern.
"""
for pattern in git_patterns:
if pattern in remote_url:
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern matching for remote URLs uses a simple in check (line 181), which could lead to false positives. For example, if a pattern is "github.com/org1" and the remote URL is "github.com/org123/repo", it would incorrectly match. Consider using more precise pattern matching, such as checking that the pattern is followed by a path separator or end of string.

Suggested change
if pattern in remote_url:
# Ensure we only match the pattern at a logical boundary, i.e. when
# it is followed by a path separator ("/" or ":") or the end of the string.
if re.search(re.escape(pattern) + r'(?=$|[/:])', remote_url):

Copilot uses AI. Check for mistakes.
@rdkcmf-jenkins
Copy link
Contributor

b'## Blackduck scan failure details

Summary: 0 violations, 0 files pending approval, 4 files pending identification.

  • Protex Server Path: /home/blackduck/github/sc/3/rdkcentral/sc

  • Commit: de4f061

Report detail: gist'

Copilot AI review requested due to automatic review settings January 7, 2026 09:24
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from de4f061 to a7a609a Compare January 7, 2026 09:24
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 18 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


def get_ticketing_config(self) -> dict[str, TicketHostCfg]:
"""Return all ticketing instance configs keyed by identifier."""
return {k: TicketHostCfg(**v) for k,v in self._ticket_config.get_config().items()}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type annotation should be dict[str, TicketHostCfg] but the method returns a dict comprehension where ValidationError exceptions during TicketHostCfg construction could be raised. This could lead to unexpected behavior. Consider handling validation errors gracefully or documenting that this method can raise ValidationError.

Suggested change
return {k: TicketHostCfg(**v) for k,v in self._ticket_config.get_config().items()}
result: dict[str, TicketHostCfg] = {}
for k, v in self._ticket_config.get_config().items():
try:
result[k] = TicketHostCfg(**v)
except ValidationError as e:
raise ValueError(f"Invalid config for {k}: {e}")
return result

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +52
def get_ticket_host_identifiers(self) -> set[str]:
"""Return all configured ticketing instance identifiers."""
return self._ticket_config.get_config().keys()
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type is documented as set[str] but dict.keys() returns a dict_keys view object, not a set. While this is often acceptable for iteration, if callers expect set-specific operations, this could cause issues. Consider returning set(self._ticket_config.get_config().keys()) to match the type annotation.

Copilot uses AI. Check for mistakes.

class RemoteUrlNotFound(ReviewException):
"""Raised when remote url matches no patterns in the config.
"""
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RemoteUrlNotFound exception class is missing a pass statement or docstring body. According to PEP 257, if a class has only a docstring and no methods, it should still have a pass statement for clarity, or the docstring should be on the same line as the class definition if it's a one-liner.

Suggested change
"""
"""
pass

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 25
def __init__(self, token: str, base_url: str):
super().__init__(token, base_url)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: SSL verification is disabled globally in the constructor (line 25) and all requests use verify=False (lines 33, 70). This makes the application vulnerable to man-in-the-middle attacks. Consider making SSL verification configurable rather than always disabled, similar to how RedmineInstance handles it with a verify_ssl parameter.

Copilot uses AI. Check for mistakes.
Comment on lines 124 to 125
comment_message (str): The body of the comment
ticket_id (str, optional): The ticket id to add the comment to. Defaults to None.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation inconsistency: The docstring states "ticket_id (str, optional): The ticket id to add the comment to. Defaults to None." However, ticket_id is not an optional parameter (no default value is defined), and passing None would likely cause an error.

Suggested change
comment_message (str): The body of the comment
ticket_id (str, optional): The ticket id to add the comment to. Defaults to None.
ticket_id (str): The ticket id to add the comment to.
comment_message (str): The body of the comment.

Copilot uses AI. Check for mistakes.
Validates if connection to the git instance is successful.
Raises:
ConnectionError: If the connection is unsuccesful.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spelling of "unsuccessful" is incorrect. It should be "unsuccessful" not "unsuccesful" (missing an 's').

Suggested change
ConnectionError: If the connection is unsuccesful.
ConnectionError: If the connection is unsuccessful.

Copilot uses AI. Check for mistakes.
return 'redmine'

def read_ticket(self, ticket_id: str) -> Ticket:
"""Read the information from a ticket and put it's contents in this object contents dict
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammatical error: "it's" should be "its" (possessive, not contraction). The docstring should read "put its contents in this object contents dict".

Suggested change
"""Read the information from a ticket and put it's contents in this object contents dict
"""Read the information from a ticket and put its contents in this object contents dict

Copilot uses AI. Check for mistakes.
sys.exit(1)

logger.info("Enter your api token: ")
api_key = input("> ")
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential security issue: The API token is read using input() instead of getpass.getpass(), which means it will be visible on the screen as the user types. This is inconsistent with line 132 where getpass is correctly used for the ticketing instance token. For consistency and security, use getpass.getpass() here as well.

Suggested change
api_key = input("> ")
api_key = getpass.getpass("> ")

Copilot uses AI. Check for mistakes.
def validate_connection(self) -> bool:
url = f"{self.base_url}/api/v4/user"
try:
r = requests.get(url, headers=self._headers(), timeout=10, verify=False)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: SSL verification is disabled with verify=False. This is inconsistent with the disabled warnings in the constructor and makes the application vulnerable to man-in-the-middle attacks.

Copilot uses AI. Check for mistakes.
params = {"state": "all", "source_branch": source_branch}
try:
r = requests.get(
url, headers=self._headers(), params=params, timeout=10, verify=False)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: SSL verification is disabled with verify=False. This is inconsistent with the disabled warnings in the constructor and makes the application vulnerable to man-in-the-middle attacks.

Copilot uses AI. Check for mistakes.
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from a7a609a to d5d1d6d Compare January 7, 2026 11:35
Copilot AI review requested due to automatic review settings January 7, 2026 11:36
@BenjiMilan BenjiMilan force-pushed the feature/BWDO-520_review branch from d5d1d6d to f823495 Compare January 7, 2026 11:36
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 17 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

def validate_connection(self) -> bool:
url = f"{self.base_url}/api/v4/user"
try:
r = requests.get(url, headers=self._headers(), timeout=10, verify=False)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SSL certificate verification is disabled by setting verify=False. This creates a security vulnerability by making the connection susceptible to man-in-the-middle attacks. Consider making SSL verification configurable through a constructor parameter, defaulting to True, similar to how RedmineInstance handles verify_ssl.

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +265
def _prompt_ticket_selection(self) -> tuple[TicketingInstance, str]:
"""Prompt the user to select a ticketing instance and enter a ticket number.
Raises:
TicketIdentifierNotFound: If the instance identifier doesn't match any
in the config.
Returns:
tuple[str, str]: The selected ticketing instance identifier and ticket
number.
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature in the docstring states it returns tuple[TicketingInstance, str], but the actual return statement on line 281 returns tuple[str, str] (the selected identifier and ticket number). The docstring should be corrected to match the actual return type.

Copilot uses AI. Check for mistakes.
try:
issue = self._instance.issue(ticket_id)
except JIRAError as e:
if 'permission' in e.text:
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error checking in line 89 uses 'permission' in e.text which could raise an AttributeError if e.text is None. Consider adding a check to ensure e.text exists before performing string operations on it.

Suggested change
if 'permission' in e.text:
error_text = getattr(e, "text", None)
if error_text and 'permission' in error_text:

Copilot uses AI. Check for mistakes.
logger.info("Enter the branch prefix (e.g ABC for feature/ABC-123_ticket): ")
branch_prefix = input("> ")
print("")

Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User input for branch_prefix is not validated. An empty string or whitespace-only input would create an invalid configuration entry. Consider adding validation to ensure non-empty input.

Suggested change
branch_prefix = branch_prefix.strip()
if not branch_prefix:
logger.error("Branch prefix cannot be empty or whitespace-only!")
sys.exit(1)

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +129
logger.info("Enter the base URL: ")
base_url = input("> ")
print("")
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User input for base_url is not validated. An empty string or whitespace-only input would create an invalid configuration entry. Consider adding validation to ensure non-empty and properly formatted URL input.

Copilot uses AI. Check for mistakes.
def _get_repo_slug(self, remote_url: str) -> str:
"""Return the repository slug (e.g. "org/repo") from a remote url."""
if remote_url.startswith("git@"):
slug = remote_url.split(":", 1)[1]
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The split operation on line 188 could raise an IndexError if the remote_url doesn't contain a colon. Consider adding validation or error handling for malformed git@ URLs.

Suggested change
slug = remote_url.split(":", 1)[1]
parts = remote_url.split(":", 1)
if len(parts) < 2 or not parts[1]:
raise ValueError(f"Malformed git@ remote URL: {remote_url}")
slug = parts[1]

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +64
pattern = input("> ")
print("")
elif provider == "gitlab":
logger.info(
"Enter the URL for the gitlab instance (e.g. https://gitlab.com "
"or https://your-instance.com): ")
url = input("> ")
print("")
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User input for pattern is not validated. An empty string or whitespace-only input would create an invalid configuration entry. Consider adding validation to ensure non-empty input.

Suggested change
pattern = input("> ")
print("")
elif provider == "gitlab":
logger.info(
"Enter the URL for the gitlab instance (e.g. https://gitlab.com "
"or https://your-instance.com): ")
url = input("> ")
print("")
while True:
pattern = input("> ").strip()
print("")
if pattern:
break
logger.error("Pattern cannot be empty. Please enter a non-empty pattern.")
elif provider == "gitlab":
logger.info(
"Enter the URL for the gitlab instance (e.g. https://gitlab.com "
"or https://your-instance.com): ")
while True:
url = input("> ").strip()
print("")
if url:
break
logger.error("URL cannot be empty. Please enter a non-empty URL.")

Copilot uses AI. Check for mistakes.
auth_type=auth_type,
project_prefix=project_prefix
)

Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive credentials (API tokens and passwords) are stored using ConfigManager which persists them to a YAML file. Consider warning users about the security implications of storing credentials in plaintext, or implementing encryption for sensitive values.

Suggested change
logger.warning(
"Security notice: the API token/password will be stored in the local "
"review configuration file, typically in plaintext. Ensure that this "
"file is kept secure and not shared with others."
)

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +85
git_cfg = GitInstanceCfg(url=url, token=api_key, provider=provider)
ReviewConfig().write_git_data(pattern, git_cfg)
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive credentials (API token) are stored using ConfigManager which persists them to a YAML file. Consider warning users about the security implications of storing credentials in plaintext, or implementing encryption for sensitive values.

Copilot uses AI. Check for mistakes.
host_identifiers = self._config.get_ticket_host_identifiers()
for identifier in host_identifiers:
# Matches the identifier, followed by - or _, followed by a number
if m := re.search(fr'{identifier}[-_]?(\d+)', branch_name):
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern uses an unescaped identifier from the config. If the identifier contains special regex characters (e.g., '.', '*', '['), this could lead to unexpected matching behavior or errors. Consider escaping the identifier using re.escape() before using it in the pattern.

Suggested change
if m := re.search(fr'{identifier}[-_]?(\d+)', branch_name):
if m := re.search(rf'{re.escape(identifier)}[-_]?(\d+)', branch_name):

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants