Did a lot of work for project migration, fixed some stuff with user migration, optimized comparisons
This commit is contained in:
parent
14c9139718
commit
66db60e2ef
@ -25,9 +25,13 @@
|
|||||||
# --no-update-existing-users Do not update existing users on Gitea (default: False)
|
# --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)
|
# --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-groups Override existing groups on Gitea (default: False) - not implemented yet
|
||||||
# --override-users Override existing users 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-groups Skip empty groups (default: False) - not implemented yet
|
||||||
# --skip-empty-projects Skip empty projects (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_USERS = False
|
||||||
NO_UPDATE_EXISTING_PROJECTS = False
|
NO_UPDATE_EXISTING_PROJECTS = False
|
||||||
|
|
||||||
OVERWRITE_EXISTING_GROUPS = False
|
INCLUDE_WIKI = False
|
||||||
OVERWRITE_EXISTING_USERS = False
|
INCLUDE_ISSUES = False
|
||||||
OVERWRITE_EXISTING_PROJECTS = False
|
INCLUDE_MERGE_REQUESTS = False
|
||||||
|
|
||||||
|
OVERRIDE_EXISTING_GROUPS = False
|
||||||
|
OVERRIDE_EXISTING_USERS = False
|
||||||
|
OVERRIDE_EXISTING_PROJECTS = False
|
||||||
|
|
||||||
ONLY_GROUPS = False
|
ONLY_GROUPS = False
|
||||||
ONLY_USERS = False
|
ONLY_USERS = False
|
||||||
@ -92,7 +100,52 @@ QUIET = False
|
|||||||
|
|
||||||
# Internal variables - Do not change
|
# Internal variables - Do not change
|
||||||
|
|
||||||
|
APP_NAME = "GitLab2Gitea"
|
||||||
|
APP_VERSION = "1.0"
|
||||||
|
APP_AUTHOR = "Zion Networks"
|
||||||
GITEA_RESERVED_USERNAMES = ["ghost", "notifications"]
|
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
|
# Imports
|
||||||
|
|
||||||
@ -101,6 +154,7 @@ import sys
|
|||||||
import argparse
|
import argparse
|
||||||
import requests
|
import requests
|
||||||
import traceback
|
import traceback
|
||||||
|
import json
|
||||||
|
|
||||||
# Set cwd to script directory
|
# 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:
|
if "NO_UPDATE_EXISTING_PROJECTS" in os.environ:
|
||||||
NO_UPDATE_EXISTING_PROJECTS = bool(os.environ["NO_UPDATE_EXISTING_PROJECTS"])
|
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:
|
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:
|
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:
|
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:
|
if "ONLY_GROUPS" in os.environ:
|
||||||
ONLY_GROUPS = bool(os.environ["ONLY_GROUPS"])
|
ONLY_GROUPS = bool(os.environ["ONLY_GROUPS"])
|
||||||
@ -232,17 +295,29 @@ if os.path.exists(".env"):
|
|||||||
if value.lower() == "true" or value == "1":
|
if value.lower() == "true" or value == "1":
|
||||||
NO_UPDATE_EXISTING_PROJECTS = True
|
NO_UPDATE_EXISTING_PROJECTS = True
|
||||||
|
|
||||||
if key == "OVERWRITE_EXISTING_GROUPS":
|
if key == "INCLUDE_WIKI":
|
||||||
if value.lower() == "true" or value == "1":
|
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":
|
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":
|
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 key == "ONLY_GROUPS":
|
||||||
if value.lower() == "true" or value == "1":
|
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-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-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("--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-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-users", help="Override existing users on Gitea", action="store_true")
|
||||||
parser.add_argument("--override-projects", help="Override existing projects 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:
|
if args.no_update_existing_projects:
|
||||||
NO_UPDATE_EXISTING_PROJECTS = True
|
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:
|
if args.override_groups:
|
||||||
OVERWRITE_EXISTING_GROUPS = True
|
OVERRIDE_EXISTING_GROUPS = True
|
||||||
|
|
||||||
if args.override_users:
|
if args.override_users:
|
||||||
OVERWRITE_EXISTING_USERS = True
|
OVERRIDE_EXISTING_USERS = True
|
||||||
|
|
||||||
if args.override_projects:
|
if args.override_projects:
|
||||||
OVERWRITE_EXISTING_PROJECTS = True
|
OVERRIDE_EXISTING_PROJECTS = True
|
||||||
|
|
||||||
if args.only_groups:
|
if args.only_groups:
|
||||||
ONLY_GROUPS = True
|
ONLY_GROUPS = True
|
||||||
@ -398,37 +485,37 @@ if ONLY_USERS and ONLY_PROJECTS:
|
|||||||
_error("Options --only-users and --only-projects are mutually exclusive!")
|
_error("Options --only-users and --only-projects are mutually exclusive!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_CREATE_MISSING_GROUPS and OVERWRITE_EXISTING_GROUPS:
|
if NO_CREATE_MISSING_GROUPS and OVERRIDE_EXISTING_GROUPS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-create-missing-groups and --override-groups are mutually exclusive!"
|
"Options --no-create-missing-groups and --override-groups are mutually exclusive!"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_CREATE_MISSING_USERS and OVERWRITE_EXISTING_USERS:
|
if NO_CREATE_MISSING_USERS and OVERRIDE_EXISTING_USERS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-create-missing-users and --override-users are mutually exclusive!"
|
"Options --no-create-missing-users and --override-users are mutually exclusive!"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_CREATE_MISSING_PROJECTS and OVERWRITE_EXISTING_PROJECTS:
|
if NO_CREATE_MISSING_PROJECTS and OVERRIDE_EXISTING_PROJECTS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-create-missing-projects and --override-projects are mutually exclusive!"
|
"Options --no-create-missing-projects and --override-projects are mutually exclusive!"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_UPDATE_EXISTING_GROUPS and OVERWRITE_EXISTING_GROUPS:
|
if NO_UPDATE_EXISTING_GROUPS and OVERRIDE_EXISTING_GROUPS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-update-existing-groups and --override-groups are mutually exclusive!"
|
"Options --no-update-existing-groups and --override-groups are mutually exclusive!"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_UPDATE_EXISTING_USERS and OVERWRITE_EXISTING_USERS:
|
if NO_UPDATE_EXISTING_USERS and OVERRIDE_EXISTING_USERS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-update-existing-users and --override-users are mutually exclusive!"
|
"Options --no-update-existing-users and --override-users are mutually exclusive!"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if NO_UPDATE_EXISTING_PROJECTS and OVERWRITE_EXISTING_PROJECTS:
|
if NO_UPDATE_EXISTING_PROJECTS and OVERRIDE_EXISTING_PROJECTS:
|
||||||
_error(
|
_error(
|
||||||
"Options --no-update-existing-projects and --override-projects are mutually exclusive!"
|
"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:
|
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:
|
def gitlab2gitea_visibility(visibility: str) -> str:
|
||||||
@ -704,6 +806,63 @@ def get_gitlab_projects() -> list:
|
|||||||
return projects
|
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
|
# Endpoint: POST /api/{GITEA_API_VERSION}/admin/users
|
||||||
def migrate_gitlab_user_to_gitea(user: dict):
|
def migrate_gitlab_user_to_gitea(user: dict):
|
||||||
|
|
||||||
@ -921,6 +1080,87 @@ def update_gitea_org(data: dict) -> dict:
|
|||||||
return group
|
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}
|
# Endpoint: GET /api/{GITEA_API_VERSION}/users/{username}
|
||||||
def get_gitea_user(username: str) -> dict:
|
def get_gitea_user(username: str) -> dict:
|
||||||
|
|
||||||
@ -950,68 +1190,86 @@ def get_gitea_user(username: str) -> dict:
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
# Endpoint: PATCH /api/{GITEA_API_VERSION}/admin/users/{username}
|
def remap_keys(d, mapping):
|
||||||
def update_gitea_user(data: dict) -> dict:
|
def nested_get(d, keys):
|
||||||
|
for key in keys.split("/"):
|
||||||
|
d = d[key]
|
||||||
|
return d
|
||||||
|
|
||||||
if not data:
|
def nested_set(d, keys, value):
|
||||||
raise Exception("Data is missing!")
|
keys = keys.split("/")
|
||||||
|
for key in keys[:-1]:
|
||||||
|
d = d.setdefault(key, {})
|
||||||
|
d[keys[-1]] = value
|
||||||
|
|
||||||
username = data["username"]
|
remapped = {}
|
||||||
current_user = get_gitea_user(username)
|
for k, v in d.items():
|
||||||
updated_user = convert_gitlab_user_to_gitea(data, current_user)
|
if k in mapping:
|
||||||
|
nested_set(remapped, mapping[k], v)
|
||||||
if is_gitea_reserved_username(username):
|
elif isinstance(v, dict):
|
||||||
_warn(f'User "{username}" is a reserved username on Gitea!')
|
remapped[k] = remap_keys(
|
||||||
return None
|
v,
|
||||||
|
{
|
||||||
if not current_user:
|
f"{k}/{subkey}": subvalue
|
||||||
raise Exception(f'User "{username}" not found on Gitea!')
|
for subkey, subvalue in mapping.items()
|
||||||
|
if subkey.startswith(f"{k}/")
|
||||||
_debug(f"REQUEST: PATCH {GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}")
|
},
|
||||||
|
)
|
||||||
response = requests.patch(
|
else:
|
||||||
f"{GITEA_URL}/api/{GITEA_API_VERSION}/admin/users/{username}",
|
remapped[k] = v
|
||||||
json=updated_user,
|
return remapped
|
||||||
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 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 = {}
|
result = {}
|
||||||
|
|
||||||
for key in userdata_a:
|
if mapping:
|
||||||
if key in userdata_b:
|
dict_a = remap_keys(dict_a, mapping)
|
||||||
if userdata_a[key] != userdata_b[key]:
|
|
||||||
result[key] = True
|
|
||||||
else:
|
|
||||||
result[key] = False
|
|
||||||
else:
|
|
||||||
result[key] = True
|
|
||||||
|
|
||||||
has_changes = False
|
def compare_dicts(a, b, to_check, path=[]):
|
||||||
for key in result:
|
all_keys = set(a.keys()).union(b.keys())
|
||||||
if result[key]:
|
if to_check is not None:
|
||||||
has_changes = True
|
all_keys = all_keys.intersection(to_check.keys())
|
||||||
break
|
|
||||||
|
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
|
return has_changes, result
|
||||||
|
|
||||||
@ -1020,15 +1278,17 @@ def convert_gitlab_user_to_gitea(user: dict, extra_data: dict = None) -> dict:
|
|||||||
|
|
||||||
gitea_user = {
|
gitea_user = {
|
||||||
"active": user["state"] == "active",
|
"active": user["state"] == "active",
|
||||||
|
"login": user["username"],
|
||||||
"login_name": user["username"],
|
"login_name": user["username"],
|
||||||
"avatar_url": user["avatar_url"],
|
"username": user["username"],
|
||||||
"created": user["created_at"],
|
|
||||||
"description": user["bio"],
|
|
||||||
"email": user["email"],
|
"email": user["email"],
|
||||||
"full_name": user["name"],
|
"full_name": user["name"],
|
||||||
"is_admin": user["is_admin"],
|
"admin": user["is_admin"],
|
||||||
"prohibit_login": user["state"] == "blocked" or user["locked"],
|
"prohibit_login": user["state"] == "blocked" or user["locked"],
|
||||||
|
"restricted": user["state"] == "blocked" or user["locked"],
|
||||||
"website": user["website_url"],
|
"website": user["website_url"],
|
||||||
|
"visibility": "private" if user["private_profile"] else "public",
|
||||||
|
"allow_create_organization": user["can_create_group"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if extra_data:
|
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:
|
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):
|
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
|
exists = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if is_gitea_reserved_organame(name):
|
||||||
|
_warn(f'Skipping group "{name}": Group name is reserved on Gitea!')
|
||||||
|
continue
|
||||||
|
|
||||||
if not exists:
|
if not exists:
|
||||||
_info(f'Creating missing group "{name}" on Gitea...')
|
_info(f'Creating missing group "{name}" on Gitea...')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
_info(f'Migrating Gitlab group "{name}" to Gitea...')
|
||||||
migrate_gitlab_group_to_gitea(gitlab_group)
|
migrate_gitlab_group_to_gitea(gitlab_group)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_exception(f'Failed to create Gitea group "{name}": {e}', 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
|
continue
|
||||||
|
|
||||||
if is_gitea_reserved_username(name):
|
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
|
continue
|
||||||
|
|
||||||
if not exists:
|
if not exists:
|
||||||
_info(f'Creating missing user "{name}" on Gitea...')
|
_info(f'Creating missing user "{name}" on Gitea...')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
_info(f'Migrating Gitlab user "{name}" to Gitea...')
|
||||||
migrate_gitlab_user_to_gitea(gitlab_user)
|
migrate_gitlab_user_to_gitea(gitlab_user)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_exception(f'Failed to create Gitea user "{name}": {e}', e)
|
_exception(f'Failed to create Gitea user "{name}": {e}', e)
|
||||||
|
|
||||||
|
|
||||||
def create_missing_projects(gitlab_projects: list, gitea_projects: list):
|
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):
|
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
|
exists = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if exists:
|
if is_gitea_reserved_organame(name):
|
||||||
_info(f'Updating existing group "{name}" on Gitea...')
|
_warn(f'Skipping group "{name}": Group name is reserved on Gitea!')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if exists:
|
||||||
try:
|
try:
|
||||||
|
_info(f'Updating existing group "{name}" on Gitea...')
|
||||||
update_gitea_org(
|
update_gitea_org(
|
||||||
{
|
{
|
||||||
"path": gitlab_group["path"],
|
"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!')
|
_warn(f'User "{name}" does not have a username and will not be updated!')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if is_gitea_reserved_username(name):
|
||||||
|
_warn(f'Skipping user "{name}": Username is reserved on Gitea!')
|
||||||
|
continue
|
||||||
|
|
||||||
if exists:
|
if exists:
|
||||||
_info(f'Updating existing user "{name}" on Gitea...')
|
|
||||||
try:
|
try:
|
||||||
|
_info(f'Updating existing user "{name}" on Gitea...')
|
||||||
update_gitea_user(gitlab_user)
|
update_gitea_user(gitlab_user)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_exception(f'Failed to update Gitea user "{name}": {e}', 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):
|
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():
|
def migrate_groups():
|
||||||
|
|
||||||
gitlab_groups = get_gitlab_groups()
|
gitlab_groups = get_gitlab_groups()
|
||||||
gitea_groups = get_gitea_groups()
|
gitea_groups = get_gitea_groups()
|
||||||
|
|
||||||
@ -1332,12 +1759,14 @@ def migrate_users():
|
|||||||
|
|
||||||
|
|
||||||
def migrate_projects():
|
def migrate_projects():
|
||||||
|
|
||||||
|
if OVERRIDE_EXISTING_PROJECTS:
|
||||||
|
_warn("EXISTING PROJECTS WILL BE OVERRIDDEN!")
|
||||||
|
|
||||||
gitlab_projects = get_gitlab_projects()
|
gitlab_projects = get_gitlab_projects()
|
||||||
gitea_projects = get_gitea_projects()
|
gitea_projects = get_gitea_projects()
|
||||||
|
|
||||||
# dump the projects to json files
|
# dump the projects to json files
|
||||||
import json
|
|
||||||
|
|
||||||
with open("gitlab_projects.json", "w") as f:
|
with open("gitlab_projects.json", "w") as f:
|
||||||
json.dump(gitlab_projects, f, indent=4)
|
json.dump(gitlab_projects, f, indent=4)
|
||||||
|
|
||||||
@ -1419,8 +1848,10 @@ def run_migration():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
_info("Gitlab2Gitea v1.0 - by Zion Networks")
|
header_info = f"{APP_NAME} v{APP_VERSION} - by {APP_AUTHOR}"
|
||||||
_info("------------------------------------")
|
|
||||||
|
_info(f"{header_info}")
|
||||||
|
_info("-" * len(header_info))
|
||||||
_info("")
|
_info("")
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
|
Reference in New Issue
Block a user