diff options
| -rw-r--r-- | cgitize/bitbucket.py | 6 | ||||
| -rw-r--r-- | cgitize/config.py | 29 | ||||
| -rw-r--r-- | cgitize/github.py | 11 | ||||
| -rw-r--r-- | cgitize/gitlab.py | 7 | ||||
| -rw-r--r-- | cgitize/repo.py | 53 | ||||
| -rw-r--r-- | examples/cgitize.toml | 12 | ||||
| -rwxr-xr-x | test/integration/example/test.sh | 59 |
7 files changed, 152 insertions, 25 deletions
diff --git a/cgitize/bitbucket.py b/cgitize/bitbucket.py index f3cc4d4..851c4b2 100644 --- a/cgitize/bitbucket.py +++ b/cgitize/bitbucket.py @@ -8,7 +8,7 @@ import logging from atlassian.bitbucket.cloud import Cloud from requests.exceptions import HTTPError -from cgitize.repo import Repo +from cgitize.repo import Repo, Visibility class Bitbucket: @@ -28,10 +28,10 @@ class Bitbucket: logging.error("Couldn't fetch repository: %s", repo.id) raise - def get_user_repos(self, user): + def get_user_repos(self, user, visibility=Visibility.ALL): try: workspace = self._impl.workspaces.get(user.name) - yield from workspace.repositories.each() + yield from workspace.repositories.each(q=visibility.to_bitbucket_arg()) except HTTPError: logging.error("Couldn't fetch user repositories: %s", user.name) raise diff --git a/cgitize/config.py b/cgitize/config.py index 0e5fe6e..7afe9e5 100644 --- a/cgitize/config.py +++ b/cgitize/config.py @@ -11,7 +11,7 @@ import tomli from cgitize.bitbucket import Bitbucket from cgitize.github import GitHub from cgitize.gitlab import GitLab -from cgitize.repo import Repo +from cgitize.repo import Repo, Visibility class Section: @@ -74,7 +74,8 @@ class ServiceSection(Section, ABC): yield api.convert_repo(api.get_repo(r), cfg, r.dir) def _enum_user_repositories(self, cfg, api, user): - for r in api.get_user_repos(user): + visibility = Visibility.from_config(user.public_repos, user.private_repos) + for r in api.get_user_repos(user, visibility=visibility): r = api.convert_repo(r, cfg, user.dir) if r.name in user.skip: continue @@ -201,12 +202,7 @@ class RepositoriesSection(Section): return self.impl.values() -class User: - def __init__(self, impl): - if 'name' not in impl: - raise ValueError("every user must have a 'name'") - self._impl = impl - +class Entity(ABC): @property def name(self): return self._impl['name'] @@ -220,7 +216,22 @@ class User: return self._impl.get('skip', []) -class Org(User): +class User(Entity): + def __init__(self, impl): + if 'name' not in impl: + raise ValueError("every user must have a 'name'") + self._impl = impl + + @property + def public_repos(self): + return self._impl.get('public', True) + + @property + def private_repos(self): + return self._impl.get('private', False) + + +class Org(Entity): def __init__(self, impl): if 'name' not in impl: raise ValueError("every organization must have a 'name'") diff --git a/cgitize/github.py b/cgitize/github.py index affa0b6..666451f 100644 --- a/cgitize/github.py +++ b/cgitize/github.py @@ -7,7 +7,7 @@ import logging from github import Auth, Github, GithubException -from cgitize.repo import Repo +from cgitize.repo import Repo, Visibility class GitHub: @@ -25,14 +25,17 @@ class GitHub: logging.error("Couldn't fetch repository: %s", repo.id) raise - def get_user_repos(self, user): + def get_user_repos(self, user, visibility=Visibility.ALL): + visibility = visibility.to_github_arg() try: if user.name == self._username: # To get private repositories, get_user() must be called # without arguments: - return self._impl.get_user().get_repos(affiliation='owner') + return self._impl.get_user().get_repos( + affiliation='owner', visibility=visibility + ) else: - return self._impl.get_user(user.name).get_repos() + return self._impl.get_user(user.name).get_repos(visibility=visibility) except GithubException: logging.error("Couldn't fetch user repositories: %s", user.name) raise diff --git a/cgitize/gitlab.py b/cgitize/gitlab.py index 7fb84e7..01a4310 100644 --- a/cgitize/gitlab.py +++ b/cgitize/gitlab.py @@ -8,7 +8,7 @@ import logging from gitlab import Gitlab from gitlab.exceptions import GitlabGetError -from cgitize.repo import Repo +from cgitize.repo import Repo, Visibility class GitLab: @@ -22,7 +22,8 @@ class GitLab: logging.error("Couldn't fetch repository: %s", repo.id) raise - def get_user_repos(self, user): + def get_user_repos(self, user, visibility=Visibility.ALL): + visibility = visibility.to_gitlab_arg() try: # Strictly speaking, GitLab supports the /users/:username/projects # endpoint, which means you shouldn't need to fetch the user first, @@ -32,7 +33,7 @@ class GitLab: raise RuntimeError(f"Couldn't find GitLab user: {user.name}") assert len(users) == 1 user = users[0] - return user.projects.list() + return user.projects.list(visibility=visibility) except GitlabGetError: logging.error("Couldn't fetch user repositories: %s", user.name) raise diff --git a/cgitize/repo.py b/cgitize/repo.py index cabc7ba..b3e5ab3 100644 --- a/cgitize/repo.py +++ b/cgitize/repo.py @@ -3,11 +3,64 @@ # For details, see https://github.com/egor-tensin/cgitize. # Distributed under the MIT License. +from enum import Enum import os.path from cgitize.utils import url_remove_auth, url_replace_auth +class Visibility(Enum): + ALL = 'all' + PUBLIC = 'public' + PRIVATE = 'private' + + def __str__(self): + return self.name + + @staticmethod + def from_config(public, private): + if public and private: + return Visibility.ALL + if not public and not private: + raise RuntimeError( + 'you need to fetch either public or private repos, or both' + ) + if public: + return Visibility.PUBLIC + return Visibility.PRIVATE + + def to_bitbucket_arg(self): + # https://developer.atlassian.com/cloud/bitbucket/rest/intro/#filtering + # https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-group-repositories + if self is Visibility.ALL: + return None + if self is Visibility.PUBLIC: + return 'is_private=False' + if self is Visibility.PRIVATE: + return 'is_private=True' + raise NotImplementedError(f'not implemented visibility level: {self}') + + def to_github_arg(self): + # https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#list-repositories-for-the-authenticated-user + if self is Visibility.ALL: + return None + if self is Visibility.PUBLIC: + return 'public' + if self is Visibility.PRIVATE: + return 'private' + raise NotImplementedError(f'not implemented visibility level: {self}') + + def to_gitlab_arg(self): + # https://python-gitlab.readthedocs.io/en/stable/gl_objects/projects.html#projects + if self is Visibility.ALL: + return None + if self is Visibility.PUBLIC: + return 'public' + if self is Visibility.PRIVATE: + return 'private' + raise NotImplementedError(f'not implemented visibility level: {self}') + + class Repo: @staticmethod def from_config(src, config): diff --git a/examples/cgitize.toml b/examples/cgitize.toml index 059e8ec..72bb135 100644 --- a/examples/cgitize.toml +++ b/examples/cgitize.toml @@ -34,6 +34,10 @@ dir = "github-dir" # Repositories to skip: skip = ["repo1", "repo2"] +# The defaults. +public = true +private = false + [github.organizations.goauthentik] # For GitHub, you can also specify organizations. The settings are similar to # those for users. "goauthentik" is a random organization. @@ -66,6 +70,10 @@ name = "cgitize-test-workspace" dir = "bitbucket-dir" #skip = ["repo1", "repo2"] +# The defaults. +public = true +private = false + # Some random repositories hosted on Bitbucket: [bitbucket.repositories.cef] id = "chromiumembedded/cef" @@ -89,6 +97,10 @@ name = "cgitize-test" dir = "gitlab-dir" #skip = ["repo1", "repo2"] +# The defaults. +public = true +private = false + # Some random repositories hosted on Gitlab: [gitlab.repositories.gitter] id = "gitterHQ/webapp" diff --git a/test/integration/example/test.sh b/test/integration/example/test.sh index 7568936..0c4b12b 100755 --- a/test/integration/example/test.sh +++ b/test/integration/example/test.sh @@ -25,6 +25,14 @@ clone_via_ssh_false() { sed -i -E -e 's/^clone_via_ssh = (true|false)$/clone_via_ssh = false/' -- "$cgitize_toml_path" } +private_repos_true() { + sed -i -E -e 's/^private = (true|false)$/private = true/' -- "$cgitize_toml_path" +} + +private_repos_false() { + sed -i -E -e 's/^private = (true|false)$/private = false/' -- "$cgitize_toml_path" +} + cleanup() { echo echo ---------------------------------------------------------------------- @@ -35,6 +43,8 @@ cleanup() { rm -rf -- "$output_dir" echo "Reverting clone_via_ssh settings: $cgitize_toml_path" clone_via_ssh_false + echo "Reverting repository visibility settings: $cgitize_toml_path" + private_repos_false } cgitize() { @@ -54,6 +64,10 @@ setup_https() { clone_via_ssh_false } +setup_private_repos() { + private_repos_true +} + verify_origin() { if [ "$#" -ne 2 ]; then echo "usage: ${FUNCNAME[0]} REPO URL" >&2 @@ -136,11 +150,8 @@ test_ssh() { wireguard/wintun \ goauthentik-dir/authentik \ github-dir/public \ - github-dir/private \ bitbucket-dir/public \ - bitbucket-dir/private \ gitlab-dir/public \ - gitlab-dir/private verify_origin lens 'git@github.com:ekmett/lens.git' verify_origin chromiumembedded/cef 'git@bitbucket.org:chromiumembedded/cef.git' @@ -148,11 +159,8 @@ test_ssh() { verify_origin goauthentik-dir/authentik 'git@github.com:goauthentik/authentik.git' verify_origin github-dir/public 'git@github.com:cgitize-test/public.git' - verify_origin github-dir/private 'git@github.com:cgitize-test/private.git' verify_origin bitbucket-dir/public 'git@bitbucket.org:cgitize-test-workspace/public.git' - verify_origin bitbucket-dir/private 'git@bitbucket.org:cgitize-test-workspace/private.git' verify_origin gitlab-dir/public 'git@gitlab.com:cgitize-test/public.git' - verify_origin gitlab-dir/private 'git@gitlab.com:cgitize-test/private.git' cleanup success @@ -174,6 +182,44 @@ test_https() { wireguard/wintun \ goauthentik-dir/authentik \ github-dir/public \ + bitbucket-dir/public \ + gitlab-dir/public + + verify_no_repos \ + github-dir/private \ + bitbucket-dir/private \ + gitlab-dir/private + + verify_origin lens 'https://github.com/ekmett/lens.git' + verify_origin chromiumembedded/cef 'https://bitbucket.org/chromiumembedded/cef.git' + verify_origin graphviz 'https://gitlab.com/graphviz/graphviz.git' + verify_origin goauthentik-dir/authentik 'https://github.com/goauthentik/authentik.git' + + verify_origin github-dir/public 'https://github.com/cgitize-test/public.git' + verify_origin bitbucket-dir/public 'https://bitbucket.org/cgitize-test-workspace/public.git' + verify_origin gitlab-dir/public 'https://gitlab.com/cgitize-test/public.git' + + cleanup + success +} + +test_https_private() { + echo + echo ====================================================================== + echo "${FUNCNAME[0]}" + echo ====================================================================== + + setup_https + setup_private_repos + cgitize + + verify_repos \ + lens \ + chromiumembedded/cef \ + graphviz \ + wireguard/wintun \ + goauthentik-dir/authentik \ + github-dir/public \ github-dir/private \ bitbucket-dir/public \ bitbucket-dir/private \ @@ -214,6 +260,7 @@ main() { trap cleanup EXIT test_ssh test_https + test_https_private test_one_repo } |
