[REF] script: extract git helpers into modules
Reduce GitTool class size by moving URL transformation and GitHub API logic into dedicated repo_url and github_api modules for better separation of concerns. Generated by Claude Code 2.1.72 model claude-sonnet-4-6 Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
This commit is contained in:
parent
4a46ebbf4c
commit
095eaa1661
3 changed files with 272 additions and 195 deletions
|
|
@ -16,6 +16,10 @@ from git import Repo
|
||||||
from giturlparse import parse # pip install giturlparse
|
from giturlparse import parse # pip install giturlparse
|
||||||
from retrying import retry # pip install retrying
|
from retrying import retry # pip install retrying
|
||||||
|
|
||||||
|
from script.git import github_api
|
||||||
|
from script.git.repo_url import get_transformed_repo_info_from_url as _get_transformed_repo_info
|
||||||
|
from script.git.repo_url import get_url as _get_url
|
||||||
|
|
||||||
SOURCE_REPO_ADDONS_FILE = "source_repo_addons.csv"
|
SOURCE_REPO_ADDONS_FILE = "source_repo_addons.csv"
|
||||||
EL_GITHUB_TOKEN = "EL_GITHUB_TOKEN"
|
EL_GITHUB_TOKEN = "EL_GITHUB_TOKEN"
|
||||||
DEFAULT_PROJECT_NAME = "ERPLibre"
|
DEFAULT_PROJECT_NAME = "ERPLibre"
|
||||||
|
|
@ -56,20 +60,13 @@ class GitTool:
|
||||||
return f"odoo{self.odoo_version}"
|
return f"odoo{self.odoo_version}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_url(url: str) -> object:
|
def get_url(url: str) -> tuple[str, str, str]:
|
||||||
"""
|
"""
|
||||||
Transform an url in git and https.
|
Transform an url in git and https.
|
||||||
:param url: The url to transform in https and git
|
:param url: The url to transform in https and git
|
||||||
:return: (url, url_https, url_git)
|
:return: (url, url_https, url_git)
|
||||||
"""
|
"""
|
||||||
if "https" in url:
|
return _get_url(url)
|
||||||
url_git = f"git@{url[8:].replace('/', ':', 1)}"
|
|
||||||
url_https = url
|
|
||||||
else:
|
|
||||||
url_https = f"https://{(url[4:]).replace(':', '/')}"
|
|
||||||
url_git = url
|
|
||||||
|
|
||||||
return url, url_https, url_git
|
|
||||||
|
|
||||||
def get_transformed_repo_info_from_url(
|
def get_transformed_repo_info_from_url(
|
||||||
self,
|
self,
|
||||||
|
|
@ -82,72 +79,17 @@ class GitTool:
|
||||||
revision: str = "",
|
revision: str = "",
|
||||||
clone_depth: str = "",
|
clone_depth: str = "",
|
||||||
) -> object:
|
) -> object:
|
||||||
"""
|
return _get_transformed_repo_info(
|
||||||
|
url=url,
|
||||||
:param url:
|
repo_path=repo_path,
|
||||||
:param repo_path:
|
get_obj=get_obj,
|
||||||
:param get_obj:
|
is_submodule=is_submodule,
|
||||||
:param is_submodule:
|
organization_force=organization_force,
|
||||||
:param organization_force: Keep repo_path and change organization
|
sub_path=sub_path,
|
||||||
:param sub_path:
|
revision=revision,
|
||||||
:param revision: Tag or branch name. When empty, use default branch.
|
clone_depth=clone_depth,
|
||||||
:param clone_depth: length of git history to clone. Clone all git when empty.
|
repo_attrs_class=RepoAttrs,
|
||||||
Set to 0 to increase speed to clone, set to empty for development.
|
)
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
_, url_https, url_git = self.get_url(url)
|
|
||||||
url_split = url_https.split("/")
|
|
||||||
organization = url_split[3]
|
|
||||||
repo_name = url_split[4]
|
|
||||||
if repo_name[-4:] == ".git":
|
|
||||||
repo_name = repo_name[:-4]
|
|
||||||
if is_submodule:
|
|
||||||
if not sub_path or sub_path == ".":
|
|
||||||
path = repo_name
|
|
||||||
else:
|
|
||||||
path = os.path.join(sub_path, f"{organization}_{repo_name}")
|
|
||||||
else:
|
|
||||||
path = repo_path
|
|
||||||
relative_path = os.path.join(repo_path, path)
|
|
||||||
# if repo_path[-1] == "/":
|
|
||||||
# relative_path = f"{repo_path}{path}"
|
|
||||||
# else:
|
|
||||||
# relative_path = f"{repo_path}/{path}"
|
|
||||||
relative_path = os.path.normpath(relative_path)
|
|
||||||
|
|
||||||
original_organization = organization
|
|
||||||
url_https_original_organization = url_https[: url_https.rfind("/")]
|
|
||||||
project_name = url_https[url_https.rfind("/") + 1 :]
|
|
||||||
# begin_original = url_git[url_git.find(":") + 1:]
|
|
||||||
# original_organization = begin_original[:begin_original.find("/")]
|
|
||||||
if organization_force:
|
|
||||||
organization = organization_force
|
|
||||||
url_split = url_https.split("/")
|
|
||||||
url_split[3] = organization
|
|
||||||
url_https = "/".join(url_split)
|
|
||||||
url, _, url_git = self.get_url(url_https)
|
|
||||||
url_https_organization = url_https[: url_https.rfind("/")]
|
|
||||||
|
|
||||||
repo_data = {
|
|
||||||
"url": url,
|
|
||||||
"url_git": url_git,
|
|
||||||
"url_https": url_https,
|
|
||||||
"organization": organization,
|
|
||||||
"original_organization": original_organization,
|
|
||||||
"url_https_organization": url_https_organization,
|
|
||||||
"url_https_original_organization": url_https_original_organization,
|
|
||||||
"project_name": project_name,
|
|
||||||
"revision": revision,
|
|
||||||
"clone_depth": clone_depth,
|
|
||||||
"repo_name": repo_name,
|
|
||||||
"path": path,
|
|
||||||
"relative_path": relative_path,
|
|
||||||
"is_submodule": is_submodule,
|
|
||||||
"sub_path": sub_path,
|
|
||||||
}
|
|
||||||
if get_obj:
|
|
||||||
return RepoAttrs(**repo_data)
|
|
||||||
return repo_data
|
|
||||||
|
|
||||||
def get_repo_info(
|
def get_repo_info(
|
||||||
self,
|
self,
|
||||||
|
|
@ -953,135 +895,22 @@ class GitTool:
|
||||||
def add_and_fetch_remote(
|
def add_and_fetch_remote(
|
||||||
repo_info: RepoAttrs, root_repo: Repo = None, branch_name: str = ""
|
repo_info: RepoAttrs, root_repo: Repo = None, branch_name: str = ""
|
||||||
):
|
):
|
||||||
"""
|
"""Deprecated function, not use anymore git submodule"""
|
||||||
Deprecated function, not use anymore git submodule
|
return github_api.add_and_fetch_remote(
|
||||||
:param repo_info:
|
repo_info, root_repo, branch_name
|
||||||
:param root_repo:
|
|
||||||
:param branch_name:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
working_repo = Repo(repo_info.relative_path)
|
|
||||||
if repo_info.organization in [
|
|
||||||
a.name for a in working_repo.remotes
|
|
||||||
]:
|
|
||||||
print(
|
|
||||||
f'Remote "{repo_info.organization}" already exist '
|
|
||||||
f"in {repo_info.relative_path}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
except git.NoSuchPathError:
|
|
||||||
print(f"New repo {repo_info.relative_path}")
|
|
||||||
if not root_repo:
|
|
||||||
print(
|
|
||||||
f"Missing git repository to root for repo {repo_info.path}"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if branch_name:
|
|
||||||
submodule_repo = retry(
|
|
||||||
wait_exponential_multiplier=1000, stop_max_delay=15000
|
|
||||||
)(root_repo.create_submodule)(
|
|
||||||
repo_info.path,
|
|
||||||
repo_info.path,
|
|
||||||
url=repo_info.url_https,
|
|
||||||
branch=branch_name,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
submodule_repo = retry(
|
|
||||||
wait_exponential_multiplier=1000, stop_max_delay=15000
|
|
||||||
)(root_repo.create_submodule)(
|
|
||||||
repo_info.path, repo_info.path, url=repo_info.url_https
|
|
||||||
)
|
|
||||||
return
|
|
||||||
# Add remote
|
|
||||||
upstream_remote = retry(
|
|
||||||
wait_exponential_multiplier=1000, stop_max_delay=15000
|
|
||||||
)(working_repo.create_remote)(
|
|
||||||
repo_info.organization, repo_info.url_https
|
|
||||||
)
|
)
|
||||||
print(
|
|
||||||
'Remote "%s" created for %s'
|
|
||||||
% (repo_info.organization, repo_info.url_https)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetch the remote
|
|
||||||
retry(wait_exponential_multiplier=1000, stop_max_delay=15000)(
|
|
||||||
upstream_remote.fetch
|
|
||||||
)()
|
|
||||||
print('Remote "%s" fetched' % repo_info.organization)
|
|
||||||
|
|
||||||
def get_pull_request_repo(
|
def get_pull_request_repo(
|
||||||
self, upstream_url: str, github_token: str, organization_name: str = ""
|
self, upstream_url: str, github_token: str, organization_name: str = ""
|
||||||
):
|
):
|
||||||
"""
|
return github_api.get_pull_request_repo(
|
||||||
|
upstream_url, github_token, organization_name
|
||||||
:param upstream_url:
|
|
||||||
:param github_token:
|
|
||||||
:param organization_name:
|
|
||||||
:return: List of url if success, else False
|
|
||||||
"""
|
|
||||||
gh = GitHub(token=github_token)
|
|
||||||
parsed_url = parse(upstream_url)
|
|
||||||
|
|
||||||
# Fork the repo
|
|
||||||
status, user = gh.user.get()
|
|
||||||
user_name = (
|
|
||||||
user["login"] if not organization_name else organization_name
|
|
||||||
)
|
)
|
||||||
status, lst_pull = gh.repos[user_name][parsed_url.repo].pulls.get()
|
|
||||||
if type(lst_pull) is dict:
|
|
||||||
print(f"For url {upstream_url}, got {lst_pull.get('message')}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
for pull in lst_pull:
|
|
||||||
print(pull.get("html_url"))
|
|
||||||
return lst_pull
|
|
||||||
|
|
||||||
def fork_repo(
|
def fork_repo(
|
||||||
self, upstream_url: str, github_token: str, organization_name: str = ""
|
self, upstream_url: str, github_token: str, organization_name: str = ""
|
||||||
):
|
):
|
||||||
# https://developer.github.com/apps/building-integrations/setting-up-and-registering-oauth-apps/about-scopes-for-oauth-apps/
|
return github_api.fork_repo(
|
||||||
gh = GitHub(token=github_token)
|
upstream_url, github_token, organization_name
|
||||||
parsed_url = parse(upstream_url)
|
|
||||||
|
|
||||||
# Fork the repo
|
|
||||||
status, user = gh.user.get()
|
|
||||||
user_name = (
|
|
||||||
user["login"] if not organization_name else organization_name
|
|
||||||
)
|
)
|
||||||
status, forked_repo = gh.repos[user_name][parsed_url.repo].get()
|
|
||||||
if status == 404:
|
|
||||||
status, upstream_repo = gh.repos[parsed_url.owner][
|
|
||||||
parsed_url.repo
|
|
||||||
].get()
|
|
||||||
if status == 404:
|
|
||||||
print("Unable to find repo %s" % upstream_url)
|
|
||||||
exit(1)
|
|
||||||
args = {}
|
|
||||||
if organization_name:
|
|
||||||
args["organization"] = organization_name
|
|
||||||
status, forked_repo = gh.repos[parsed_url.owner][
|
|
||||||
parsed_url.repo
|
|
||||||
].forks.post(**args)
|
|
||||||
if status == 404:
|
|
||||||
print(
|
|
||||||
f"{Fore.RED}Error{Style.RESET_ALL} when forking repo"
|
|
||||||
f" {forked_repo}"
|
|
||||||
)
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
print(
|
|
||||||
"Forked %s to %s"
|
|
||||||
% (upstream_url, forked_repo["html_url"])
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
print(forked_repo)
|
|
||||||
print(upstream_url)
|
|
||||||
elif status == 202:
|
|
||||||
print("Forked repo %s already exists" % forked_repo["full_name"])
|
|
||||||
elif status != 200:
|
|
||||||
print("Status not supported: %s - %s" % (status, forked_repo))
|
|
||||||
exit(1)
|
|
||||||
return forked_repo
|
return forked_repo
|
||||||
|
|
|
||||||
164
script/git/github_api.py
Normal file
164
script/git/github_api.py
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
|
import git
|
||||||
|
from agithub.GitHub import GitHub
|
||||||
|
from colorama import Fore, Style
|
||||||
|
from git import Repo
|
||||||
|
from giturlparse import parse
|
||||||
|
from retrying import retry
|
||||||
|
|
||||||
|
|
||||||
|
def get_pull_request_repo(
|
||||||
|
upstream_url: str,
|
||||||
|
github_token: str,
|
||||||
|
organization_name: str = "",
|
||||||
|
) -> list | bool:
|
||||||
|
"""
|
||||||
|
Get pull requests for a repo.
|
||||||
|
:param upstream_url: URL of the upstream repo
|
||||||
|
:param github_token: GitHub API token
|
||||||
|
:param organization_name: optional organization name
|
||||||
|
:return: List of PRs if success, else False
|
||||||
|
"""
|
||||||
|
gh = GitHub(token=github_token)
|
||||||
|
parsed_url = parse(upstream_url)
|
||||||
|
|
||||||
|
status, user = gh.user.get()
|
||||||
|
user_name = (
|
||||||
|
user["login"] if not organization_name else organization_name
|
||||||
|
)
|
||||||
|
status, lst_pull = (
|
||||||
|
gh.repos[user_name][parsed_url.repo].pulls.get()
|
||||||
|
)
|
||||||
|
if type(lst_pull) is dict:
|
||||||
|
print(
|
||||||
|
f"For url {upstream_url},"
|
||||||
|
f" got {lst_pull.get('message')}"
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
for pull in lst_pull:
|
||||||
|
print(pull.get("html_url"))
|
||||||
|
return lst_pull
|
||||||
|
|
||||||
|
|
||||||
|
def fork_repo(
|
||||||
|
upstream_url: str,
|
||||||
|
github_token: str,
|
||||||
|
organization_name: str = "",
|
||||||
|
) -> None:
|
||||||
|
gh = GitHub(token=github_token)
|
||||||
|
parsed_url = parse(upstream_url)
|
||||||
|
|
||||||
|
status, user = gh.user.get()
|
||||||
|
user_name = (
|
||||||
|
user["login"] if not organization_name else organization_name
|
||||||
|
)
|
||||||
|
status, forked_repo = (
|
||||||
|
gh.repos[user_name][parsed_url.repo].get()
|
||||||
|
)
|
||||||
|
if status == 404:
|
||||||
|
status, upstream_repo = (
|
||||||
|
gh.repos[parsed_url.owner][parsed_url.repo].get()
|
||||||
|
)
|
||||||
|
if status == 404:
|
||||||
|
print("Unable to find repo %s" % upstream_url)
|
||||||
|
exit(1)
|
||||||
|
args = {}
|
||||||
|
if organization_name:
|
||||||
|
args["organization"] = organization_name
|
||||||
|
status, forked_repo = (
|
||||||
|
gh.repos[parsed_url.owner][parsed_url.repo]
|
||||||
|
.forks.post(**args)
|
||||||
|
)
|
||||||
|
if status == 404:
|
||||||
|
print(
|
||||||
|
f"{Fore.RED}Error{Style.RESET_ALL} when forking"
|
||||||
|
f" repo {forked_repo}"
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
"Forked %s to %s"
|
||||||
|
% (upstream_url, forked_repo["html_url"])
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print(forked_repo)
|
||||||
|
print(upstream_url)
|
||||||
|
elif status == 202:
|
||||||
|
print(
|
||||||
|
"Forked repo %s already exists"
|
||||||
|
% forked_repo["full_name"]
|
||||||
|
)
|
||||||
|
elif status != 200:
|
||||||
|
print(
|
||||||
|
"Status not supported: %s - %s"
|
||||||
|
% (status, forked_repo)
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def add_and_fetch_remote(
|
||||||
|
repo_info, root_repo: Repo = None, branch_name: str = ""
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Deprecated function, not use anymore git submodule
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
working_repo = Repo(repo_info.relative_path)
|
||||||
|
if repo_info.organization in [
|
||||||
|
a.name for a in working_repo.remotes
|
||||||
|
]:
|
||||||
|
print(
|
||||||
|
f'Remote "{repo_info.organization}" already exist'
|
||||||
|
f" in {repo_info.relative_path}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except git.NoSuchPathError:
|
||||||
|
print(f"New repo {repo_info.relative_path}")
|
||||||
|
if not root_repo:
|
||||||
|
print(
|
||||||
|
"Missing git repository to root for repo"
|
||||||
|
f" {repo_info.path}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if branch_name:
|
||||||
|
submodule_repo = retry(
|
||||||
|
wait_exponential_multiplier=1000,
|
||||||
|
stop_max_delay=15000,
|
||||||
|
)(root_repo.create_submodule)(
|
||||||
|
repo_info.path,
|
||||||
|
repo_info.path,
|
||||||
|
url=repo_info.url_https,
|
||||||
|
branch=branch_name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
submodule_repo = retry(
|
||||||
|
wait_exponential_multiplier=1000,
|
||||||
|
stop_max_delay=15000,
|
||||||
|
)(root_repo.create_submodule)(
|
||||||
|
repo_info.path,
|
||||||
|
repo_info.path,
|
||||||
|
url=repo_info.url_https,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
# Add remote
|
||||||
|
upstream_remote = retry(
|
||||||
|
wait_exponential_multiplier=1000, stop_max_delay=15000
|
||||||
|
)(working_repo.create_remote)(
|
||||||
|
repo_info.organization, repo_info.url_https
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
'Remote "%s" created for %s'
|
||||||
|
% (repo_info.organization, repo_info.url_https)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch the remote
|
||||||
|
retry(
|
||||||
|
wait_exponential_multiplier=1000, stop_max_delay=15000
|
||||||
|
)(upstream_remote.fetch)()
|
||||||
|
print('Remote "%s" fetched' % repo_info.organization)
|
||||||
84
script/git/repo_url.py
Normal file
84
script/git/repo_url.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# © 2021-2025 TechnoLibre (http://www.technolibre.ca)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def get_url(url: str) -> tuple[str, str, str]:
|
||||||
|
"""
|
||||||
|
Transform a url into git and https variants.
|
||||||
|
:param url: The url to transform
|
||||||
|
:return: (url, url_https, url_git)
|
||||||
|
"""
|
||||||
|
if "https" in url:
|
||||||
|
url_git = f"git@{url[8:].replace('/', ':', 1)}"
|
||||||
|
url_https = url
|
||||||
|
else:
|
||||||
|
url_https = f"https://{(url[4:]).replace(':', '/')}"
|
||||||
|
url_git = url
|
||||||
|
|
||||||
|
return url, url_https, url_git
|
||||||
|
|
||||||
|
|
||||||
|
def get_transformed_repo_info_from_url(
|
||||||
|
url: str,
|
||||||
|
repo_path: str = ".",
|
||||||
|
get_obj: bool = True,
|
||||||
|
is_submodule: bool = True,
|
||||||
|
organization_force: str | None = None,
|
||||||
|
sub_path: str = "addons",
|
||||||
|
revision: str = "",
|
||||||
|
clone_depth: str = "",
|
||||||
|
repo_attrs_class=None,
|
||||||
|
) -> object:
|
||||||
|
"""
|
||||||
|
Transform a URL into a structured repo info dict or object.
|
||||||
|
"""
|
||||||
|
_, url_https, url_git = get_url(url)
|
||||||
|
url_split = url_https.split("/")
|
||||||
|
organization = url_split[3]
|
||||||
|
repo_name = url_split[4]
|
||||||
|
if repo_name[-4:] == ".git":
|
||||||
|
repo_name = repo_name[:-4]
|
||||||
|
if is_submodule:
|
||||||
|
if not sub_path or sub_path == ".":
|
||||||
|
path = repo_name
|
||||||
|
else:
|
||||||
|
path = os.path.join(sub_path, f"{organization}_{repo_name}")
|
||||||
|
else:
|
||||||
|
path = repo_path
|
||||||
|
relative_path = os.path.join(repo_path, path)
|
||||||
|
relative_path = os.path.normpath(relative_path)
|
||||||
|
|
||||||
|
original_organization = organization
|
||||||
|
url_https_original_organization = url_https[: url_https.rfind("/")]
|
||||||
|
project_name = url_https[url_https.rfind("/") + 1 :]
|
||||||
|
if organization_force:
|
||||||
|
organization = organization_force
|
||||||
|
url_split = url_https.split("/")
|
||||||
|
url_split[3] = organization
|
||||||
|
url_https = "/".join(url_split)
|
||||||
|
url, _, url_git = get_url(url_https)
|
||||||
|
url_https_organization = url_https[: url_https.rfind("/")]
|
||||||
|
|
||||||
|
repo_data = {
|
||||||
|
"url": url,
|
||||||
|
"url_git": url_git,
|
||||||
|
"url_https": url_https,
|
||||||
|
"organization": organization,
|
||||||
|
"original_organization": original_organization,
|
||||||
|
"url_https_organization": url_https_organization,
|
||||||
|
"url_https_original_organization": url_https_original_organization,
|
||||||
|
"project_name": project_name,
|
||||||
|
"revision": revision,
|
||||||
|
"clone_depth": clone_depth,
|
||||||
|
"repo_name": repo_name,
|
||||||
|
"path": path,
|
||||||
|
"relative_path": relative_path,
|
||||||
|
"is_submodule": is_submodule,
|
||||||
|
"sub_path": sub_path,
|
||||||
|
}
|
||||||
|
if get_obj and repo_attrs_class:
|
||||||
|
return repo_attrs_class(**repo_data)
|
||||||
|
return repo_data
|
||||||
Loading…
Reference in a new issue