From 66db60e2ef23f43c083f286bced490c098bc8c7e Mon Sep 17 00:00:00 2001 From: Enrico Ludwig Date: Thu, 18 Jul 2024 18:20:30 +0200 Subject: [PATCH] Did a lot of work for project migration, fixed some stuff with user migration, optimized comparisons --- gitlab2gitea/gitlab2gitea.py | 615 +++++++++++++++++++++++++++++------ 1 file changed, 523 insertions(+), 92 deletions(-) diff --git a/gitlab2gitea/gitlab2gitea.py b/gitlab2gitea/gitlab2gitea.py index b3fe7ae..05bf699 100644 --- a/gitlab2gitea/gitlab2gitea.py +++ b/gitlab2gitea/gitlab2gitea.py @@ -25,9 +25,13 @@ # --no-update-existing-users Do not update existing users on Gitea (default: False) # --no-update-existing-projects Do not update existing projects on Gitea (default: False) # +# --include-wiki Include wiki repositories (default: False) - not implemented yet +# --include-issues Include issues repositories (default: False) - not implemented yet +# --include-merge-requests Include merge requests repositories (default: False) - not implemented yet +# # --override-groups Override existing groups on Gitea (default: False) - not implemented yet # --override-users Override existing users on Gitea (default: False) - not implemented yet -# --override-projects Override existing projects on Gitea (default: False) - not implemented yet +# --override-projects Override existing projects on Gitea (default: False) # # --skip-empty-groups Skip empty groups (default: False) - not implemented yet # --skip-empty-projects Skip empty projects (default: False) - not implemented yet @@ -73,9 +77,13 @@ NO_UPDATE_EXISTING_GROUPS = False NO_UPDATE_EXISTING_USERS = False NO_UPDATE_EXISTING_PROJECTS = False -OVERWRITE_EXISTING_GROUPS = False -OVERWRITE_EXISTING_USERS = False -OVERWRITE_EXISTING_PROJECTS = False +INCLUDE_WIKI = False +INCLUDE_ISSUES = False +INCLUDE_MERGE_REQUESTS = False + +OVERRIDE_EXISTING_GROUPS = False +OVERRIDE_EXISTING_USERS = False +OVERRIDE_EXISTING_PROJECTS = False ONLY_GROUPS = False ONLY_USERS = False @@ -92,7 +100,52 @@ QUIET = False # Internal variables - Do not change +APP_NAME = "GitLab2Gitea" +APP_VERSION = "1.0" +APP_AUTHOR = "Zion Networks" GITEA_RESERVED_USERNAMES = ["ghost", "notifications"] +GITEA_RESERVED_ORGANAMES = [ + "api", + "assets", + "attachments", + "avatar", + "commit", + "commits", + "debug", + "error", + "explore", + "faq", + "issues", + "mail", + "milestone", + "new", + "notifications", + "org", + "organizations", + "plugins", + "pull", + "pulls", + "repo", + "repositories", + "script", + "user", + "users", +] +GITEA_RESERVED_REPONAMES = [ + "api", + "assets", + "issues", + "labels", + "milestones", + "notifications", + "projects", + "pr", + "pulls", + "repo", + "repos", + "settings", + "wiki", +] # Imports @@ -101,6 +154,7 @@ import sys import argparse import requests import traceback +import json # Set cwd to script directory @@ -143,14 +197,23 @@ if "NO_UPDATE_EXISTING_USERS" in os.environ: if "NO_UPDATE_EXISTING_PROJECTS" in os.environ: NO_UPDATE_EXISTING_PROJECTS = bool(os.environ["NO_UPDATE_EXISTING_PROJECTS"]) +if "INCLUDE_WIKI" in os.environ: + INCLUDE_WIKI = bool(os.environ["INCLUDE_WIKI"]) + +if "INCLUDE_ISSUES" in os.environ: + INCLUDE_ISSUES = bool(os.environ["INCLUDE_ISSUES"]) + +if "INCLUDE_MERGE_REQUESTS" in os.environ: + INCLUDE_MERGE_REQUESTS = bool(os.environ["INCLUDE_MERGE_REQUESTS"]) + if "OVERWRITE_EXISTING_GROUPS" in os.environ: - OVERWRITE_EXISTING_GROUPS = bool(os.environ["OVERWRITE_EXISTING_GROUPS"]) + OVERRIDE_EXISTING_GROUPS = bool(os.environ["OVERRIDE_EXISTING_GROUPS"]) if "OVERWRITE_EXISTING_USERS" in os.environ: - OVERWRITE_EXISTING_USERS = bool(os.environ["OVERWRITE_EXISTING_USERS"]) + OVERRIDE_EXISTING_USERS = bool(os.environ["OVERRIDE_EXISTING_USERS"]) if "OVERWRITE_EXISTING_PROJECTS" in os.environ: - OVERWRITE_EXISTING_PROJECTS = bool(os.environ["OVERWRITE_EXISTING_PROJECTS"]) + OVERRIDE_EXISTING_PROJECTS = bool(os.environ["OVERRIDE_EXISTING_PROJECTS"]) if "ONLY_GROUPS" in os.environ: ONLY_GROUPS = bool(os.environ["ONLY_GROUPS"]) @@ -232,17 +295,29 @@ if os.path.exists(".env"): if value.lower() == "true" or value == "1": NO_UPDATE_EXISTING_PROJECTS = True - if key == "OVERWRITE_EXISTING_GROUPS": + if key == "INCLUDE_WIKI": if value.lower() == "true" or value == "1": - OVERWRITE_EXISTING_GROUPS = True + INCLUDE_WIKI = True - if key == "OVERWRITE_EXISTING_USERS": + if key == "INCLUDE_ISSUES": if value.lower() == "true" or value == "1": - OVERWRITE_EXISTING_USERS = True + INCLUDE_ISSUES = True - if key == "OVERWRITE_EXISTING_PROJECTS": + if key == "INCLUDE_MERGE_REQUESTS": if value.lower() == "true" or value == "1": - OVERWRITE_EXISTING_PROJECTS = True + INCLUDE_MERGE_REQUESTS = True + + if key == "OVERRIDE_EXISTING_GROUPS": + if value.lower() == "true" or value == "1": + OVERRIDE_EXISTING_GROUPS = True + + if key == "OVERRIDE_EXISTING_USERS": + if value.lower() == "true" or value == "1": + OVERRIDE_EXISTING_USERS = True + + if key == "OVERRIDE_EXISTING_PROJECTS": + if value.lower() == "true" or value == "1": + OVERRIDE_EXISTING_PROJECTS = True if key == "ONLY_GROUPS": if value.lower() == "true" or value == "1": @@ -296,6 +371,9 @@ parser.add_argument("--no-create-missing-projects", help="Do not create missing parser.add_argument("--no-update-existing-groups", help="Do not update existing groups on Gitea", action="store_true") parser.add_argument("--no-update-existing-users", help="Do not update existing users on Gitea", action="store_true") parser.add_argument("--no-update-existing-projects", help="Do not update existing projects on Gitea", action="store_true") +parser.add_argument("--include-wiki", help="Include wiki repositories", action="store_true") +parser.add_argument("--include-issues", help="Include issues repositories", action="store_true") +parser.add_argument("--include-merge-requests", help="Include merge requests repositories", action="store_true") parser.add_argument("--override-groups", help="Override existing groups on Gitea", action="store_true") parser.add_argument("--override-users", help="Override existing users on Gitea", action="store_true") parser.add_argument("--override-projects", help="Override existing projects on Gitea", action="store_true") @@ -348,14 +426,23 @@ if args.no_update_existing_users: if args.no_update_existing_projects: NO_UPDATE_EXISTING_PROJECTS = True +if args.include_wiki: + INCLUDE_WIKI = True + +if args.include_issues: + INCLUDE_ISSUES = True + +if args.include_merge_requests: + INCLUDE_MERGE_REQUESTS = True + if args.override_groups: - OVERWRITE_EXISTING_GROUPS = True + OVERRIDE_EXISTING_GROUPS = True if args.override_users: - OVERWRITE_EXISTING_USERS = True + OVERRIDE_EXISTING_USERS = True if args.override_projects: - OVERWRITE_EXISTING_PROJECTS = True + OVERRIDE_EXISTING_PROJECTS = True if args.only_groups: ONLY_GROUPS = True @@ -398,37 +485,37 @@ if ONLY_USERS and ONLY_PROJECTS: _error("Options --only-users and --only-projects are mutually exclusive!") sys.exit(1) -if NO_CREATE_MISSING_GROUPS and OVERWRITE_EXISTING_GROUPS: +if NO_CREATE_MISSING_GROUPS and OVERRIDE_EXISTING_GROUPS: _error( "Options --no-create-missing-groups and --override-groups are mutually exclusive!" ) sys.exit(1) -if NO_CREATE_MISSING_USERS and OVERWRITE_EXISTING_USERS: +if NO_CREATE_MISSING_USERS and OVERRIDE_EXISTING_USERS: _error( "Options --no-create-missing-users and --override-users are mutually exclusive!" ) sys.exit(1) -if NO_CREATE_MISSING_PROJECTS and OVERWRITE_EXISTING_PROJECTS: +if NO_CREATE_MISSING_PROJECTS and OVERRIDE_EXISTING_PROJECTS: _error( "Options --no-create-missing-projects and --override-projects are mutually exclusive!" ) sys.exit(1) -if NO_UPDATE_EXISTING_GROUPS and OVERWRITE_EXISTING_GROUPS: +if NO_UPDATE_EXISTING_GROUPS and OVERRIDE_EXISTING_GROUPS: _error( "Options --no-update-existing-groups and --override-groups are mutually exclusive!" ) sys.exit(1) -if NO_UPDATE_EXISTING_USERS and OVERWRITE_EXISTING_USERS: +if NO_UPDATE_EXISTING_USERS and OVERRIDE_EXISTING_USERS: _error( "Options --no-update-existing-users and --override-users are mutually exclusive!" ) sys.exit(1) -if NO_UPDATE_EXISTING_PROJECTS and OVERWRITE_EXISTING_PROJECTS: +if NO_UPDATE_EXISTING_PROJECTS and OVERRIDE_EXISTING_PROJECTS: _error( "Options --no-update-existing-projects and --override-projects are mutually exclusive!" ) @@ -510,7 +597,22 @@ def _exception(exception, custom_message=None): def is_gitea_reserved_username(username: str) -> bool: - return username in GITEA_RESERVED_USERNAMES + return username.lower() in [name.lower() for name in GITEA_RESERVED_USERNAMES] + + +def is_gitea_reserved_organame(organame: str) -> bool: + return organame.lower() in [name.lower() for name in GITEA_RESERVED_ORGANAMES] + + +def is_gitea_reserved_reponame(reponame: str) -> bool: + return reponame.lower() in [name.lower() for name in GITEA_RESERVED_REPONAMES] + + +def is_gitlab_project_in_subgroup(project: dict) -> bool: + return ( + project["namespace"]["kind"] == "group" + and project["namespace"]["parent_id"] is not None + ) def gitlab2gitea_visibility(visibility: str) -> str: @@ -704,6 +806,63 @@ def get_gitlab_projects() -> list: return projects +# Endpoint: POST /api/{GITLAB_API_VERSION}/repos/migrate +def migrate_gitlab_project_to_gitea(gitlab_project: dict): + + if not gitlab_project: + raise Exception("GitLab project is missing!") + + # Create Gitea project + + _debug(f"REQUEST: POST {GITEA_URL}/api/{GITEA_API_VERSION}/repos/migrate") + + response = requests.post( + f"{GITEA_URL}/api/{GITEA_API_VERSION}/repos/migrate", + json={ + "auth_token": GITEA_TOKEN, + "repo_name": gitlab_project["path"], + "repo_owner": ( + gitlab_project["namespace"]["path"] + if gitlab_project["namespace"]["kind"] == "group" + else gitlab_project["owner"]["username"] + ), + "description": gitlab_project["description"], + "clone_addr": gitlab_project["http_url_to_repo"], + "service": "gitlab", + "issues": INCLUDE_ISSUES, + "pull_requests": INCLUDE_MERGE_REQUESTS, + "wiki": INCLUDE_WIKI, + "releases": True, + "labels": True, + "lfs": True, + "milestones": True, + "mirror": False, + # fmt: off + "private": gitlab2gitea_visibility(gitlab_project["visibility"]) == "private", + # fmt: on + }, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"token {GITEA_TOKEN}", + }, + ) + + _trace(f"RESPONSE: {response.json()}") + + if response.status_code != 201: + response_message = ( + response.json()["message"] + if "message" in response.json() + else "Unknown error" + ) + raise Exception(f"Failed to create Gitea project: {response_message}") + else: + project = response.json() + + return project + + # Endpoint: POST /api/{GITEA_API_VERSION}/admin/users def migrate_gitlab_user_to_gitea(user: dict): @@ -921,6 +1080,87 @@ def update_gitea_org(data: dict) -> dict: return group +# Endpoint: PATCH /api/{GITEA_API_VERSION}/admin/users/{username} +def update_gitea_user(data: dict) -> dict: + + if not data: + raise Exception("Data is missing!") + + username = data["username"] + current_user = get_gitea_user(username) + updated_user = convert_gitlab_user_to_gitea(data) + + to_compare = { + "active": True, + "login": True, + "login_name": True, + "username": True, + "email": True, + "full_name": True, + "admin": True, + "prohibit_login": True, + "restricted": True, + "website": True, + "visibility": True, + } + + mapping = { + "is_admin": "admin", + } + + ignore = {"allow_create_organization": True} + + is_different, results = cmp_dicts( + current_user, updated_user, to_compare, mapping, ignore + ) + + _trace(f"COMPARISON: {results}") + + if not is_different: + _info(f'User "{username}" is already up-to-date on Gitea') + return current_user + + if is_gitea_reserved_username(username): + _warn(f'User "{username}" is a reserved username on Gitea!') + return None + + if not current_user: + raise Exception(f'User "{username}" not found on Gitea!') + + _debug(f"REQUEST: PATCH {GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}") + + # TODO seems to have issues, since users do not get updated properly + response = requests.patch( + f"{GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}", + json=updated_user, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"token {GITEA_TOKEN}", + }, + ) + + _trace(f"RESPONSE: {response.json()}") + + if response.status_code != 200: + response_message = ( + response.json()["message"] + if "message" in response.json() + else "Unknown error" + ) + raise Exception(f"Failed to update Gitea user: {response_message}") + else: + user = response.json() + + return user + + +def update_gitea_project(data: dict) -> dict: + + _warn("Function update_gitea_project is not implemented yet!") + pass + + # Endpoint: GET /api/{GITEA_API_VERSION}/users/{username} def get_gitea_user(username: str) -> dict: @@ -950,68 +1190,86 @@ def get_gitea_user(username: str) -> dict: return user -# Endpoint: PATCH /api/{GITEA_API_VERSION}/admin/users/{username} -def update_gitea_user(data: dict) -> dict: +def remap_keys(d, mapping): + def nested_get(d, keys): + for key in keys.split("/"): + d = d[key] + return d - if not data: - raise Exception("Data is missing!") + def nested_set(d, keys, value): + keys = keys.split("/") + for key in keys[:-1]: + d = d.setdefault(key, {}) + d[keys[-1]] = value - username = data["username"] - current_user = get_gitea_user(username) - updated_user = convert_gitlab_user_to_gitea(data, current_user) - - if is_gitea_reserved_username(username): - _warn(f'User "{username}" is a reserved username on Gitea!') - return None - - if not current_user: - raise Exception(f'User "{username}" not found on Gitea!') - - _debug(f"REQUEST: PATCH {GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}") - - response = requests.patch( - f"{GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}", - json=updated_user, - headers={ - "Content-Type": "application/json", - "Accept": "application/json", - "Authorization": f"token {GITEA_TOKEN}", - }, - ) - - _trace(f"RESPONSE: {response.json()}") - - if response.status_code != 200: - response_message = ( - response.json()["message"] - if "message" in response.json() - else "Unknown error" - ) - raise Exception(f"Failed to update Gitea user: {response_message}") - else: - user = response.json() - - return user + remapped = {} + for k, v in d.items(): + if k in mapping: + nested_set(remapped, mapping[k], v) + elif isinstance(v, dict): + remapped[k] = remap_keys( + v, + { + f"{k}/{subkey}": subvalue + for subkey, subvalue in mapping.items() + if subkey.startswith(f"{k}/") + }, + ) + else: + remapped[k] = v + return remapped -def cmp_gitea_userdata(userdata_a: dict, userdata_b: dict) -> bool: +def is_ignored(path_to_check, ignore_dict): + def nested_get(d, keys): + for key in keys: + if key in d: + d = d[key] + else: + return False + return True if isinstance(d, dict) and d else False + return nested_get(ignore_dict, path_to_check) + + +def cmp_dicts( + dict_a: dict, + dict_b: dict, + check: dict = None, + mapping: dict = None, + ignore: dict = None, +) -> bool: result = {} - for key in userdata_a: - if key in userdata_b: - if userdata_a[key] != userdata_b[key]: - result[key] = True - else: - result[key] = False - else: - result[key] = True + if mapping: + dict_a = remap_keys(dict_a, mapping) - has_changes = False - for key in result: - if result[key]: - has_changes = True - break + def compare_dicts(a, b, to_check, path=[]): + all_keys = set(a.keys()).union(b.keys()) + if to_check is not None: + all_keys = all_keys.intersection(to_check.keys()) + + for key in all_keys: + current_path = path + [key] + if ignore and is_ignored(current_path, ignore): + continue + + if key in a and key in b: + if ( + isinstance(a[key], dict) + and isinstance(b[key], dict) + and to_check + and key in to_check + ): + compare_dicts(a[key], b[key], to_check[key], current_path) + else: + result[tuple(current_path)] = a[key] != b[key] + else: + result[tuple(current_path)] = True + + compare_dicts(dict_a, dict_b, check) + + has_changes = any(result.values()) return has_changes, result @@ -1020,15 +1278,17 @@ def convert_gitlab_user_to_gitea(user: dict, extra_data: dict = None) -> dict: gitea_user = { "active": user["state"] == "active", + "login": user["username"], "login_name": user["username"], - "avatar_url": user["avatar_url"], - "created": user["created_at"], - "description": user["bio"], + "username": user["username"], "email": user["email"], "full_name": user["name"], - "is_admin": user["is_admin"], + "admin": user["is_admin"], "prohibit_login": user["state"] == "blocked" or user["locked"], + "restricted": user["state"] == "blocked" or user["locked"], "website": user["website_url"], + "visibility": "private" if user["private_profile"] else "public", + "allow_create_organization": user["can_create_group"], } if extra_data: @@ -1115,7 +1375,45 @@ def cmp_gitlab_gitea_users(gitlab_users: list, gitea_users: list) -> dict: def cmp_gitlab_gitea_projects(gitlab_projects: list, gitea_projects: list) -> dict: - return {}, 0 + compare_result = {} + missing_matches = 0 + + for gitlab_project in gitlab_projects: + exists = False + name = gitlab_project["path"] + owner = gitlab_project["namespace"]["path"] + + for gitea_project in gitea_projects["data"]: + # fmt: off + if (name == gitea_project["name"] and owner == gitea_project["owner"]["username"]): + exists = True + break + + if exists: + _info(f'GITLAB: Project "{owner}/{name}" exists on both GitLab and Gitea') + else: + _warn(f'GITLAB: Project "{owner}/{name}" exists on GitLab only') + missing_matches += 1 + + compare_result[name] = 0 if exists else 1 + + for gitea_project in gitea_projects["data"]: + exists = False + name = gitea_project["name"] + owner = gitea_project["owner"]["username"] + + for gitlab_project in gitlab_projects: + # fmt: off + if name == gitlab_project["path"] and owner == gitlab_project["namespace"]["path"]: + exists = True + break + # fmt: on + + if not exists: + _warn(f'GITEA: Project "{owner}/{name}" exists on Gitea only') + compare_result[name] = 2 + + return compare_result, missing_matches def create_missing_groups(gitlab_groups: list, gitea_groups: list): @@ -1129,10 +1427,15 @@ def create_missing_groups(gitlab_groups: list, gitea_groups: list): exists = True break + if is_gitea_reserved_organame(name): + _warn(f'Skipping group "{name}": Group name is reserved on Gitea!') + continue + if not exists: _info(f'Creating missing group "{name}" on Gitea...') try: + _info(f'Migrating Gitlab group "{name}" to Gitea...') migrate_gitlab_group_to_gitea(gitlab_group) except Exception as e: _exception(f'Failed to create Gitea group "{name}": {e}', e) @@ -1160,20 +1463,61 @@ def create_missing_users(gitlab_users: list, gitea_users: list): continue if is_gitea_reserved_username(name): - _warn(f'User "{name}" is a reserved username on Gitea!') + _warn(f'Skipping user "{name}": Username is reserved on Gitea!') continue if not exists: _info(f'Creating missing user "{name}" on Gitea...') try: + _info(f'Migrating Gitlab user "{name}" to Gitea...') migrate_gitlab_user_to_gitea(gitlab_user) except Exception as e: _exception(f'Failed to create Gitea user "{name}": {e}', e) def create_missing_projects(gitlab_projects: list, gitea_projects: list): - pass + + for gitlab_project in gitlab_projects: + exists = False + name = gitlab_project["path"] + group = gitlab_project["namespace"]["path"] + + for gitea_project in gitea_projects["data"]: + if ( + name == gitea_project["name"] + and group == gitea_project["owner"]["username"] + ): + exists = True + break + + if is_gitea_reserved_reponame(name): + _warn(f'Skipping project "{name}": Project name is reserved on Gitea!') + continue + + if is_gitlab_project_in_subgroup(gitlab_project): + _warn( + f'Skipping project "{name}": Project is in a subgroup and not supported by Gitea!' + ) + continue + + if not exists: + _info(f'Creating missing project "{name}" on Gitea...') + + try: + _info(f'Migrating Gitlab project "{name}" to Gitea...') + migrate_gitlab_project_to_gitea(gitlab_project) + + if DEBUG: + _warn("DEBUG MODE ENABLED - BREAKING AFTER FIRST PROJECT") + break + + except Exception as e: + _exception(f'Failed to create Gitea project "{name}": {e}', e) + + if DEBUG: + _warn("DEBUG MODE ENABLED - BREAKING AFTER FIRST PROJECT") + break def update_existing_groups(gitlab_groups: list, gitea_groups: list): @@ -1186,10 +1530,13 @@ def update_existing_groups(gitlab_groups: list, gitea_groups: list): exists = True break - if exists: - _info(f'Updating existing group "{name}" on Gitea...') + if is_gitea_reserved_organame(name): + _warn(f'Skipping group "{name}": Group name is reserved on Gitea!') + continue + if exists: try: + _info(f'Updating existing group "{name}" on Gitea...') update_gitea_org( { "path": gitlab_group["path"], @@ -1226,9 +1573,13 @@ def update_existing_users(gitlab_users: list, gitea_users: list): _warn(f'User "{name}" does not have a username and will not be updated!') continue + if is_gitea_reserved_username(name): + _warn(f'Skipping user "{name}": Username is reserved on Gitea!') + continue + if exists: - _info(f'Updating existing user "{name}" on Gitea...') try: + _info(f'Updating existing user "{name}" on Gitea...') update_gitea_user(gitlab_user) except Exception as e: _exception(f'Failed to update Gitea user "{name}": {e}', e) @@ -1237,10 +1588,86 @@ def update_existing_users(gitlab_users: list, gitea_users: list): def update_existing_projects(gitlab_projects: list, gitea_projects: list): - pass + + # update existing projects + for gitlab_project in gitlab_projects: + exists = False + name = gitlab_project["path"] + group = gitlab_project["namespace"]["path"] + + for gitea_project in gitea_projects["data"]: + if ( + name == gitea_project["name"] + and group == gitea_project["owner"]["username"] + ): + exists = True + break + + if is_gitea_reserved_reponame(name): + _warn(f'Skipping project "{name}": Project name is reserved on Gitea!') + continue + + if is_gitlab_project_in_subgroup(gitlab_project): + _warn( + f'Skipping project "{name}": Project is in a subgroup and not supported by Gitea!' + ) + continue + + if exists: + try: + _info(f'Updating existing project "{name}" on Gitea...') + if OVERRIDE_EXISTING_PROJECTS: + delete_gitea_project(gitea_project) + migrate_gitlab_project_to_gitea(gitlab_project) + else: + update_gitea_project(gitlab_project) + + if DEBUG: + _warn("DEBUG MODE ENABLED - BREAKING AFTER FIRST PROJECT") + break + + except Exception as e: + _exception(f'Failed to update Gitea project "{name}": {e}', e) + + if DEBUG: + _warn("DEBUG MODE ENABLED - BREAKING AFTER FIRST PROJECT") + break + else: + _warn(f'Project "{name}" does not exist on Gitea!') + + +# ENDPOINT: DELETE /api/{GITEA_API_VERSION}/repos/{owner}/{repo} +def delete_gitea_project(project: dict): + + owner = project["owner"]["username"] + repo = project["name"] + + _debug(f"REQUEST: DELETE {GITEA_URL}/api/{GITEA_API_VERSION}/repos/{owner}/{repo}") + + response = requests.delete( + f"{GITEA_URL}/api/{GITEA_API_VERSION}/repos/{owner}/{repo}", + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"token {GITEA_TOKEN}", + }, + ) + + _trace(f"RESPONSE: {response.json()}") + + if response.status_code != 204: + response_message = ( + response.json()["message"] + if "message" in response.json() + else "Unknown error" + ) + raise Exception(f"Failed to delete Gitea project: {response_message}") + else: + _info(f'Project "{owner}/{repo}" deleted on Gitea') def migrate_groups(): + gitlab_groups = get_gitlab_groups() gitea_groups = get_gitea_groups() @@ -1332,12 +1759,14 @@ def migrate_users(): def migrate_projects(): + + if OVERRIDE_EXISTING_PROJECTS: + _warn("EXISTING PROJECTS WILL BE OVERRIDDEN!") + gitlab_projects = get_gitlab_projects() gitea_projects = get_gitea_projects() # dump the projects to json files - import json - with open("gitlab_projects.json", "w") as f: json.dump(gitlab_projects, f, indent=4) @@ -1419,8 +1848,10 @@ def run_migration(): def main(): - _info("Gitlab2Gitea v1.0 - by Zion Networks") - _info("------------------------------------") + header_info = f"{APP_NAME} v{APP_VERSION} - by {APP_AUTHOR}" + + _info(f"{header_info}") + _info("-" * len(header_info)) _info("") if sys.version_info < (3, 6):