From 0fa2782343bd9d3815c64272b67dec5b30a0df03 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Fri, 19 Jul 2024 20:46:38 -0400 Subject: [PATCH] feat: github>forgejo migration script --- README.md | 26 ++++++++- migrate_repos_gh_to_forgejo.py | 99 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 migrate_repos_gh_to_forgejo.py diff --git a/README.md b/README.md index d96db71..e7b7bcb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,27 @@ # bag-of-tricks -Random utilities and one-off scripts. \ No newline at end of file +Random utilities and one-off scripts. + +## Expectations + +Scripts contained here are expected to be runnable via `pipx` and to define their dependencies using [inline +metadata](https://packaging.python.org/en/latest/specifications/inline-script-metadata/#script-type). + +```py +# /// script +# dependencies = [ +# "package1", +# "package2" +# ] +# /// + +import package1 +import package2 + +# Logic +``` + +## Usage + +Provided that scripts have an inline `script` block that defines any extra dependencies they expect, `pipx run ...args` is all that's needed to execute scripts in an isolated environment. + diff --git a/migrate_repos_gh_to_forgejo.py b/migrate_repos_gh_to_forgejo.py new file mode 100644 index 0000000..688239d --- /dev/null +++ b/migrate_repos_gh_to_forgejo.py @@ -0,0 +1,99 @@ +""" +Repository migration script + +Facilitates the migration from Github to Forgejo by importing +all public/private repositories owned by the user attached to +the personal token included. + +Expects a single argument that points to a json configuration +shaped like Config. +""" + +# /// script +# dependencies = [ +# "httpx", +# "pydantic" +# ] +# /// + +import sys + +import httpx +import pydantic + + +class Config(pydantic.BaseModel): + gh_user: str + gh_token: str + gh_host: str + gh_api_host: str + + forgejo_host: str + forgejo_user: str + forgejo_token: str + + +def get_config(path: str) -> Config: + return Config.parse_file(path) + + +def get_gh_client(config: Config): + return httpx.Client( + headers={ + "Authorization": f"Bearer {config.gh_token}", + "X-Github-Api-Version": "2022-11-28", + }, + base_url=config.gh_api_host, + ) + + +def get_forgejo_client(config: Config): + return httpx.Client( + headers={"Authorization": f"token {config.forgejo_token}"}, + base_url=config.forgejo_host, + ) + + +def get_repositories_from_github(config: Config) -> list[str]: + """Gets all repositories for given user.""" + + with get_gh_client(config) as client: + response = client.get( + f"/user/repos", params={"per_page": 100, "affiliation": "owner"} + ) + + for repository in response.json(): + full_name = repository["full_name"] + + if not full_name.startswith(config.gh_user): + print(f"Skipped {full_name}: not owned.") + continue + + yield full_name + + +def migrate_to_forgejo(repository_name: str, config: Config): + with get_forgejo_client(config) as client: + response = client.post( + "/repos/migrate", + data={ + "auth_username": config.gh_user, + "auth_token": config.gh_token, + "clone_addr": f"{config.gh_host}/{repository_name}.git", + "repo_name": repository_name.split("/")[1], + }, + timeout=None, + ) + + if response.status_code == 409: + print(f"Did not migrate {repository_name}: already exists") + elif response.status_code == 201: + print(f"Migrated {repository_name}") + + +if __name__ == "__main__": + config = get_config(sys.argv[1]) + + for repo in get_repositories_from_github(config): + print(f"Migrating {repo}...") + migrate_to_forgejo(repo, config)