Added help_docs feature.

This commit is contained in:
Eyal Sharon
2025-03-20 16:32:16 +02:00
parent b087458e33
commit 5e7e353670
12 changed files with 599 additions and 2 deletions

View File

@ -67,6 +67,33 @@ class BitbucketProvider(GitProvider):
except Exception:
return ""
def get_git_repo_url(self, pr_url: str=None) -> str: #bitbucket does not support issue url, so ignore param
try:
parsed_url = urlparse(self.pr_url)
return f"{parsed_url.scheme}://{parsed_url.netloc}/{self.workspace_slug}/{self.repo_slug}.git"
except Exception as e:
get_logger().exception(f"url is not a valid merge requests url: {self.pr_url}")
return ""
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
scheme_and_netloc = None
if repo_git_url:
parsed_git_url = urlparse(repo_git_url)
scheme_and_netloc = parsed_git_url.scheme + "://" + parsed_git_url.netloc
repo_path = parsed_git_url.path.split('.git')[0][1:] #/<workspace>/<repo>.git -> <workspace>/<repo>
if repo_path.count('/') != 1:
get_logger().error(f"repo_git_url is not a valid git repo url: {repo_git_url}")
return ("", "")
workspace_name, project_name = repo_path.split('/')
else:
parsed_pr_url = urlparse(self.pr_url)
scheme_and_netloc = parsed_pr_url.scheme + "://" + parsed_pr_url.netloc
workspace_name, project_name = (self.workspace_slug, self.repo_slug)
prefix = f"{scheme_and_netloc}/{workspace_name}/{project_name}/src/{desired_branch}"
suffix = "" #None
return (prefix, suffix)
def publish_code_suggestions(self, code_suggestions: list) -> bool:
"""
Publishes code suggestions as comments on the PR.

View File

@ -138,6 +138,31 @@ class BitbucketServerProvider(GitProvider):
return False
return True
def get_git_repo_url(self, pr_url: str=None) -> str: #bitbucket server does not support issue url, so ignore param
try:
parsed_url = urlparse(self.pr_url)
return f"{parsed_url.scheme}://{parsed_url.netloc}/scm/{self.workspace_slug.lower()}/{self.repo_slug.lower()}.git"
except Exception as e:
get_logger().exception(f"url is not a valid merge requests url: {self.pr_url}")
return ""
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
workspace_name = None
project_name = None
if not repo_git_url:
workspace_name = self.workspace_slug
project_name = self.repo_slug
else:
repo_path = repo_git_url.split('.git')[0].split('scm/')[-1]
if repo_path.count('/') == 1: # Has to have the form <workspace>/<repo>
workspace_name, project_name = repo_path.split('/')
if not workspace_name or not project_name:
get_logger().error(f"workspace_name or project_name not found in context, either git url: {repo_git_url} or uninitialized workspace/project.")
return ("", "")
prefix = f"{self.bitbucket_server_url}/projects/{workspace_name}/repos/{project_name}/browse"
suffix = f"?at=refs%2Fheads%2F{desired_branch}"
return (prefix, suffix)
def set_pr(self, pr_url: str):
self.workspace_slug, self.repo_slug, self.pr_num = self._parse_pr_url(pr_url)
self.pr = self._get_pr()

View File

@ -1,6 +1,6 @@
from abc import ABC, abstractmethod
# enum EDIT_TYPE (ADDED, DELETED, MODIFIED, RENAMED)
from typing import Optional
from typing import Optional, Tuple
from pr_agent.algo.types import FilePatchInfo
from pr_agent.algo.utils import Range, process_description
@ -14,6 +14,19 @@ class GitProvider(ABC):
def is_supported(self, capability: str) -> bool:
pass
#Given a url (issues or PR/MR) - get the .git repo url to which they belong. Needs to be implemented by the provider.
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
get_logger().warning("Not implemented! Returning empty url")
return ""
# Given a git repo url, return prefix and suffix of the provider in order to view a given file belonging to that repo. Needs to be implemented by the provider.
# For example: For a git: https://git_provider.com/MY_PROJECT/MY_REPO.git then it should return ('https://git_provider.com/projects/MY_PROJECT/repos/MY_REPO', '?=<SOME HEADER>')
# so that to properly view the file: docs/readme.md -> <PREFIX>/docs/readme.md<SUFFIX> -> https://git_provider.com/projects/MY_PROJECT/repos/MY_REPO/docs/readme.md?=<SOME HEADER>)
def get_canonical_url_parts(self, repo_git_url:str, desired_branch:str) -> Tuple[str, str]:
get_logger().warning("Not implemented! Returning empty prefix and suffix")
return ("", "")
@abstractmethod
def get_files(self) -> list:
pass

View File

@ -63,6 +63,53 @@ class GithubProvider(GitProvider):
def is_supported(self, capability: str) -> bool:
return True
def _get_owner_and_repo_path(self, given_url: str) -> str:
try:
repo_path = None
if 'issues' in given_url:
repo_path, _ = self._parse_issue_url(given_url)
elif 'pull' in given_url:
repo_path, _ = self._parse_pr_url(given_url)
elif given_url.endswith('.git'):
parsed_url = urlparse(given_url)
repo_path = (parsed_url.path.split('.git')[0])[1:] # /<owner>/<repo>.git -> <owner>/<repo>
if not repo_path:
get_logger().error(f"url is neither an issues url nor a pr url nor a valid git url: {given_url}. Returning empty result.")
return ""
return repo_path
except Exception as e:
get_logger().exception(f"unable to parse url: {given_url}. Returning empty result.")
return ""
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
repo_path = self._get_owner_and_repo_path(issues_or_pr_url)
return f"{issues_or_pr_url.split(repo_path)[0]}{repo_path}.git"
def get_canonical_url_parts(self, repo_git_url:str, desired_branch:str) -> Tuple[str, str]:
owner = None
repo = None
scheme_and_netloc = None
if repo_git_url: #If user provided an external git url, which may be different than what this provider was initialized with, we cannot use self.repo
repo_path = self._get_owner_and_repo_path(repo_git_url)
parsed_git_url = urlparse(repo_git_url)
scheme_and_netloc = parsed_git_url.scheme + "://" + parsed_git_url.netloc
if repo_path.count('/') == 1: #Has to have the form <owner>/<repo>
owner, repo = repo_path.split('/')
else:
get_logger().error(f"Invalid repo_path: {repo_path} from repo_git_url: {repo_git_url}")
return ("", "")
if (not owner or not repo) and self.repo: #"else" - User did not provide an external git url, use self.repo object:
owner, repo = self.repo.split('/')
scheme_and_netloc = self.base_url_html
if not any([scheme_and_netloc, owner, repo]): #"else": Not invoked from a PR context,but no provided git url for context
get_logger().error(f"Unable to get canonical url parts since missing context (PR or explicit git url)")
return ("", "")
prefix = f"{scheme_and_netloc}/{owner}/{repo}/blob/{desired_branch}"
suffix = "" # github does not add a suffix
return (prefix, suffix)
def get_pr_url(self) -> str:
return self.pr.html_url

View File

@ -57,6 +57,38 @@ class GitLabProvider(GitProvider):
return False
return True
def _get_project_path_from_pr_or_issue_url(self, pr_or_issue_url: str) -> str:
repo_project_path = None
if 'issues' in pr_or_issue_url:
#replace 'issues' with 'merge_requests', since gitlab provider does not support issue urls, just to get the git repo url:
pr_or_issue_url = pr_or_issue_url.replace('issues', 'merge_requests')
if 'merge_requests' in pr_or_issue_url:
repo_project_path, _ = self._parse_merge_request_url(pr_or_issue_url)
if not repo_project_path:
get_logger().error(f"url is not a valid merge requests url: {pr_or_issue_url}")
return ""
return repo_project_path
def get_git_repo_url(self, issues_or_pr_url: str) -> str:
provider_url = issues_or_pr_url
repo_path = self._get_project_path_from_pr_or_issue_url(issues_or_pr_url)
if not repo_path:
return ""
return f"{provider_url.split(repo_path)[0]}{repo_path}.git"
def get_canonical_url_parts(self, repo_git_url:str=None, desired_branch:str=None) -> Tuple[str, str]:
repo_path = ""
if not repo_git_url and not self.pr_url:
get_logger().error("Cannot get canonical URL parts: missing either context PR URL or a repo GIT URL")
return ("", "")
if not repo_git_url: #Use PR url as context
repo_path = self._get_project_path_from_pr_or_issue_url(self.pr_url)
else: #Use repo git url
repo_path = repo_git_url.split('.git')[0].split('.com/')[-1]
prefix = f"{self.gitlab_url}/{repo_path}/-/blob/{desired_branch}"
suffix = "?ref_type=heads" # gitlab cloud adds this suffix. gitlab server does not, but it is harmless.
return (prefix, suffix)
@property
def pr(self):
'''The GitLab terminology is merge request (MR) instead of pull request (PR)'''