Loading .gitlab-ci.yml +3 −2 Original line number Diff line number Diff line include: # $TBC_NAMESPACE is a group variable; can be globally overridden # Docker template - component: "gitlab.com/$TBC_NAMESPACE/docker/gitlab-ci-docker@5.9" - component: "gitlab.com/$TBC_NAMESPACE/docker/gitlab-ci-docker@5" inputs: healthcheck-disabled: true sbom-disabled: true Loading @@ -9,9 +9,10 @@ include: prod-publish-strategy: "auto" release-extra-tags: "latest \\g<major>.\\g<minor>\\g<build> \\g<major>\\g<build>" # Python template - component: "gitlab.com/$TBC_NAMESPACE/python/gitlab-ci-python@6.7" - component: "gitlab.com/$TBC_NAMESPACE/python/gitlab-ci-python@6" inputs: image: "registry.hub.docker.com/library/python:3.12" ruff-enabled: true sbom-disabled: true package-enabled: true release-enabled: true Loading gitlab_cp/sync.py +73 −76 Original line number Diff line number Diff line import argparse import base64 from http.client import HTTPResponse import json import os import re import shutil import ssl from enum import Enum from git import Remote, Repo from logging import Logger from pathlib import Path import shutil from typing import Optional, Union from urllib.request import Request, urlretrieve, urlopen from typing import Optional from urllib.request import urlretrieve from git import GitError, Repo from gitlab import ( Gitlab, GitlabCreateError, Loading @@ -21,10 +17,7 @@ from gitlab import ( GitlabListError, GitlabUpdateError, ) from gitlab.v4.objects import ProjectCommit from gitlab.v4.objects import Group from gitlab.v4.objects import Project from gitlab.v4.objects import ProjectRelease from gitlab.v4.objects import Group, Project, ProjectCommit LOGGER = Logger(__name__) Loading Loading @@ -173,7 +166,7 @@ class Synchronizer: # == resp2.headers.get("content-length") # ) def get_or_create_group(self, group_path: str) -> Group: def get_or_create_group(self, group_path: str) -> Optional[Group]: if not group_path: # root path (emtpy string) return None Loading Loading @@ -231,7 +224,7 @@ class Synchronizer: return dest_releases = dest_project.releases.list(all=True) for src_release in src_releases: tag_name = src_release.tag_name tag_name: str = src_release.tag_name dest_release = next( filter( lambda rel: rel.tag_name == tag_name, Loading Loading @@ -320,15 +313,13 @@ class Synchronizer: print( f" - git repository: {AnsiColors.HGRAY}unchanged{AnsiColors.RESET} ({src_default_branch} on #{src_latest_commit.get_id()})" ) else: return # Git sync is required repo_dir = self.work_dir / self.rel_path(src_project.path_with_namespace) shutil.rmtree(path=repo_dir, ignore_errors=True) src_repo_url: str = src_project.http_url_to_repo # print( # f"... git clone source repository ({AnsiColors.HGREEN}{src_repo_url}{AnsiColors.RESET})" # ) if self.src_client.private_token: # insert login/password in Git https url src_repo_url = src_repo_url.replace( Loading @@ -336,6 +327,7 @@ class Synchronizer: ) # git clone --bare "$src_repo_url" "$repo_name" try: repo = Repo.clone_from( url=src_repo_url, to_path=repo_dir, Loading @@ -343,12 +335,16 @@ class Synchronizer: allow_unsafe_options=True, env={} if self.src_client.ssl_verify else {"GIT_SSL_NO_VERIFY": "1"}, ) except GitError as ge: print( f" - source repo: git clone {AnsiColors.HRED}failed{AnsiColors.RESET}", ge, ) self.handle_error(ge) return # if project already exists: unprotect default branch first if dest_latest_commit: # print( # f"... unprotect {AnsiColors.HGREEN}{src_default_branch}{AnsiColors.RESET} branch (allow failure)" # ) if not self.dry_run: try: dest_project.protectedbranches.delete(src_default_branch) Loading @@ -363,11 +359,8 @@ class Synchronizer: dest_repo_url = ( dest_project.http_url_to_repo if dest_project else f"https://gitlab.dummy/{dest_project_path}.git" else f"https://gitlab.dummy/{src_project.path_with_namespace}.git" ) # print( # f"... git push (force) destination repository ({AnsiColors.HGREEN}{dest_repo_url}{AnsiColors.RESET})" # ) if self.dest_client.private_token: # insert login/password in Git https url # shellcheck disable=SC2001 Loading @@ -376,6 +369,7 @@ class Synchronizer: ) # git ${INSECURE:+-c http.sslVerify=false} push --force "$dest_repo_url" --tags "$src_default_branch" try: if not self.dry_run: dest = repo.create_remote("dest", dest_repo_url) dest.push( Loading @@ -389,11 +383,13 @@ class Synchronizer: else {"GIT_SSL_NO_VERIFY": "1"} ), ).raise_if_error() except GitError as ge: print( f" - dest repo: git push --force {AnsiColors.HRED}failed{AnsiColors.RESET}", ge, ) self.handle_error(ge) # after sync: protect default branch again # print( # f"... protect {AnsiColors.HGREEN}{src_default_branch}{AnsiColors.RESET} branch" # ) if not self.dry_run: try: dest_project.protectedbranches.create({"name": src_default_branch}) Loading Loading @@ -504,7 +500,7 @@ class Synchronizer: src_avatar_url = src_project.avatar_url src_web_url = src_project.web_url dest_avatar_url = dest_project.avatar_url if dest_project else None dest_web_url = dest_project.web_url dest_web_url = dest_project.web_url if dest_project else None if ( src_avatar_url and dest_avatar_url Loading Loading @@ -641,7 +637,8 @@ class Synchronizer: dest_avatar_url = dest_group.avatar_url if dest_group else None if ( src_avatar_url and (dest_avatar_url == None or self.update_avatar) and dest_group and (dest_avatar_url is None or self.update_avatar) # and not self.look_same_resources(src_avatar_url, dest_avatar_url) ): try: Loading poetry.lock +85 −358 File changed.Preview size limit exceeded, changes collapsed. Show changes pyproject.toml +17 −8 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ pytest = "^7.0.0" pytest-cov = "^4.0.0" pytest-env = "^0.8.0" mypy = "^1.0.0" pylint = "^2.17.5" black = "^24.1.1" ruff = "^0.4.1" [tool.pytest.ini_options] # this project uses "pytest" for unit testing Loading @@ -35,12 +34,22 @@ testpaths = [ "tests", ] [tool.black] # this project uses "Black" as code formatter # see: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#usage line-length = 88 target-version = ['py312'] include = '\.pyi?$' [tool.ruff.lint] extend-select = ["I"] # isort ignore = ["F841"] [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" # Like Black, indent with spaces, rather than tabs. indent-style = "space" # Like Black, respect magic trailing commas. skip-magic-trailing-comma = false # Like Black, automatically detect the appropriate line ending. line-ending = "auto" [tool.mypy] # this project uses "mypy" as a static type checker Loading Loading
.gitlab-ci.yml +3 −2 Original line number Diff line number Diff line include: # $TBC_NAMESPACE is a group variable; can be globally overridden # Docker template - component: "gitlab.com/$TBC_NAMESPACE/docker/gitlab-ci-docker@5.9" - component: "gitlab.com/$TBC_NAMESPACE/docker/gitlab-ci-docker@5" inputs: healthcheck-disabled: true sbom-disabled: true Loading @@ -9,9 +9,10 @@ include: prod-publish-strategy: "auto" release-extra-tags: "latest \\g<major>.\\g<minor>\\g<build> \\g<major>\\g<build>" # Python template - component: "gitlab.com/$TBC_NAMESPACE/python/gitlab-ci-python@6.7" - component: "gitlab.com/$TBC_NAMESPACE/python/gitlab-ci-python@6" inputs: image: "registry.hub.docker.com/library/python:3.12" ruff-enabled: true sbom-disabled: true package-enabled: true release-enabled: true Loading
gitlab_cp/sync.py +73 −76 Original line number Diff line number Diff line import argparse import base64 from http.client import HTTPResponse import json import os import re import shutil import ssl from enum import Enum from git import Remote, Repo from logging import Logger from pathlib import Path import shutil from typing import Optional, Union from urllib.request import Request, urlretrieve, urlopen from typing import Optional from urllib.request import urlretrieve from git import GitError, Repo from gitlab import ( Gitlab, GitlabCreateError, Loading @@ -21,10 +17,7 @@ from gitlab import ( GitlabListError, GitlabUpdateError, ) from gitlab.v4.objects import ProjectCommit from gitlab.v4.objects import Group from gitlab.v4.objects import Project from gitlab.v4.objects import ProjectRelease from gitlab.v4.objects import Group, Project, ProjectCommit LOGGER = Logger(__name__) Loading Loading @@ -173,7 +166,7 @@ class Synchronizer: # == resp2.headers.get("content-length") # ) def get_or_create_group(self, group_path: str) -> Group: def get_or_create_group(self, group_path: str) -> Optional[Group]: if not group_path: # root path (emtpy string) return None Loading Loading @@ -231,7 +224,7 @@ class Synchronizer: return dest_releases = dest_project.releases.list(all=True) for src_release in src_releases: tag_name = src_release.tag_name tag_name: str = src_release.tag_name dest_release = next( filter( lambda rel: rel.tag_name == tag_name, Loading Loading @@ -320,15 +313,13 @@ class Synchronizer: print( f" - git repository: {AnsiColors.HGRAY}unchanged{AnsiColors.RESET} ({src_default_branch} on #{src_latest_commit.get_id()})" ) else: return # Git sync is required repo_dir = self.work_dir / self.rel_path(src_project.path_with_namespace) shutil.rmtree(path=repo_dir, ignore_errors=True) src_repo_url: str = src_project.http_url_to_repo # print( # f"... git clone source repository ({AnsiColors.HGREEN}{src_repo_url}{AnsiColors.RESET})" # ) if self.src_client.private_token: # insert login/password in Git https url src_repo_url = src_repo_url.replace( Loading @@ -336,6 +327,7 @@ class Synchronizer: ) # git clone --bare "$src_repo_url" "$repo_name" try: repo = Repo.clone_from( url=src_repo_url, to_path=repo_dir, Loading @@ -343,12 +335,16 @@ class Synchronizer: allow_unsafe_options=True, env={} if self.src_client.ssl_verify else {"GIT_SSL_NO_VERIFY": "1"}, ) except GitError as ge: print( f" - source repo: git clone {AnsiColors.HRED}failed{AnsiColors.RESET}", ge, ) self.handle_error(ge) return # if project already exists: unprotect default branch first if dest_latest_commit: # print( # f"... unprotect {AnsiColors.HGREEN}{src_default_branch}{AnsiColors.RESET} branch (allow failure)" # ) if not self.dry_run: try: dest_project.protectedbranches.delete(src_default_branch) Loading @@ -363,11 +359,8 @@ class Synchronizer: dest_repo_url = ( dest_project.http_url_to_repo if dest_project else f"https://gitlab.dummy/{dest_project_path}.git" else f"https://gitlab.dummy/{src_project.path_with_namespace}.git" ) # print( # f"... git push (force) destination repository ({AnsiColors.HGREEN}{dest_repo_url}{AnsiColors.RESET})" # ) if self.dest_client.private_token: # insert login/password in Git https url # shellcheck disable=SC2001 Loading @@ -376,6 +369,7 @@ class Synchronizer: ) # git ${INSECURE:+-c http.sslVerify=false} push --force "$dest_repo_url" --tags "$src_default_branch" try: if not self.dry_run: dest = repo.create_remote("dest", dest_repo_url) dest.push( Loading @@ -389,11 +383,13 @@ class Synchronizer: else {"GIT_SSL_NO_VERIFY": "1"} ), ).raise_if_error() except GitError as ge: print( f" - dest repo: git push --force {AnsiColors.HRED}failed{AnsiColors.RESET}", ge, ) self.handle_error(ge) # after sync: protect default branch again # print( # f"... protect {AnsiColors.HGREEN}{src_default_branch}{AnsiColors.RESET} branch" # ) if not self.dry_run: try: dest_project.protectedbranches.create({"name": src_default_branch}) Loading Loading @@ -504,7 +500,7 @@ class Synchronizer: src_avatar_url = src_project.avatar_url src_web_url = src_project.web_url dest_avatar_url = dest_project.avatar_url if dest_project else None dest_web_url = dest_project.web_url dest_web_url = dest_project.web_url if dest_project else None if ( src_avatar_url and dest_avatar_url Loading Loading @@ -641,7 +637,8 @@ class Synchronizer: dest_avatar_url = dest_group.avatar_url if dest_group else None if ( src_avatar_url and (dest_avatar_url == None or self.update_avatar) and dest_group and (dest_avatar_url is None or self.update_avatar) # and not self.look_same_resources(src_avatar_url, dest_avatar_url) ): try: Loading
pyproject.toml +17 −8 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ pytest = "^7.0.0" pytest-cov = "^4.0.0" pytest-env = "^0.8.0" mypy = "^1.0.0" pylint = "^2.17.5" black = "^24.1.1" ruff = "^0.4.1" [tool.pytest.ini_options] # this project uses "pytest" for unit testing Loading @@ -35,12 +34,22 @@ testpaths = [ "tests", ] [tool.black] # this project uses "Black" as code formatter # see: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#usage line-length = 88 target-version = ['py312'] include = '\.pyi?$' [tool.ruff.lint] extend-select = ["I"] # isort ignore = ["F841"] [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" # Like Black, indent with spaces, rather than tabs. indent-style = "space" # Like Black, respect magic trailing commas. skip-magic-trailing-comma = false # Like Black, automatically detect the appropriate line ending. line-ending = "auto" [tool.mypy] # this project uses "mypy" as a static type checker Loading