# Description: Script to migrate repositories from GitLab to Gitea # Author: Enrico Ludwig # Version: 1.0 # Date: 2021-01-06 # # Precedence of settings: arguments > .env file > environment variables > default values # # Usage: # python3 gitlab2gitea.py [options] # # --gitlab-token GitLab access token # --gitlab-url GitLab URL # --gitlab-api-version GitLab API version (default: v4) # --gitea-token Gitea access token # --gitea-url Gitea URL # --gitea-api-version Gitea API version (default: v1) # # --quiet Enable quiet mode (default: False) # --debug Enable debug mode (default: False) # --dry-run Enable dry-run mode (default: False) # --log-file Log file (default: gitlab2gitea.log) # If not set, logs will be only printed to stdout # --append-log Append log file (default: False) # # Example: # python3 gitlab2gitea.py \ # --gitlab-url \ # --gitlab-token \ # --gitea-url \ # --gitea-token # Settings - GitLab GITLAB_URL = '' GITLAB_TOKEN = '' GITLAB_API_VERSION = 'v4' # Settings - Gitea GITEA_URL = '' GITEA_TOKEN = '' GITEA_API_VERSION = 'v1' # Settings - General DEBUG = False DRY_RUN = False LOG_FILE = 'gitlab2gitea.log' APPEND_LOG = False QUIET = False # Imports import os import argparse import requests # Set cwd to script directory os.chdir(os.path.dirname(os.path.realpath(__file__))) # Read environment variables if 'GITLAB_URL' in os.environ: GITLAB_URL = os.environ['GITLAB_URL'] if 'GITLAB_TOKEN' in os.environ: GITLAB_TOKEN = os.environ['GITLAB_TOKEN'] if 'GITLAB_API_VERSION' in os.environ: GITLAB_API_VERSION = os.environ['GITLAB_API_VERSION'] if 'GITEA_URL' in os.environ: GITEA_URL = os.environ['GITEA_URL'] if 'GITEA_TOKEN' in os.environ: GITEA_TOKEN = os.environ['GITEA_TOKEN'] if 'GITEA_API_VERSION' in os.environ: GITEA_API_VERSION = os.environ['GITEA_API_VERSION'] if 'QUIET' in os.environ: QUIET = bool(os.environ['QUIET']) if 'DEBUG' in os.environ: DEBUG = bool(os.environ['DEBUG']) if 'DRY_RUN' in os.environ: DRY_RUN = bool(os.environ['DRY_RUN']) if 'LOG_FILE' in os.environ: LOG_FILE = os.environ['LOG_FILE'] if 'APPEND_LOG' in os.environ: APPEND_LOG = bool(os.environ['APPEND_LOG']) # Read .env file if exists and override environment variables if os.path.exists('.env'): with open('.env', 'r') as env_file: for line in env_file: # skip comments, empty lines and lines without '=' if line.startswith('#') or not '=' in line: continue key, value = line.strip().split('=') if key == 'GITLAB_API_VERSION': GITLAB_API_VERSION = value if key == 'GITLAB_URL': GITLAB_URL = value if key == 'GITLAB_TOKEN': GITLAB_TOKEN = value if key == 'GITEA_URL': GITEA_URL = value if key == 'GITEA_TOKEN': GITEA_TOKEN = value if key == 'GITEA_API_VERSION': GITEA_API_VERSION = value if key == 'QUIET': QUIET = bool(value) if key == 'DEBUG': DEBUG = bool(value) if key == 'DRY_RUN': DRY_RUN = bool(value) if key == 'LOG_FILE': LOG_FILE = value if key == 'APPEND_LOG': APPEND_LOG = bool(value) # Read arguments and override environment variables parser = argparse.ArgumentParser(description='Script to migrate repositories from GitLab to Gitea') parser.add_argument('--gitlab-api-version', help='GitLab API version', default='v4') parser.add_argument('--gitlab-token', help='GitLab access token') parser.add_argument('--gitea-token', help='Gitea access token') parser.add_argument('--gitlab-url', help='GitLab URL') parser.add_argument('--gitea-url', help='Gitea URL') parser.add_argument('--gitea-api-version', help='Gitea API version', default='v1') parser.add_argument('--quiet', help='Enable quiet mode', action='store_true') parser.add_argument('--debug', help='Enable debug mode', action='store_true') parser.add_argument('--dry-run', help='Enable dry-run mode', action='store_true') parser.add_argument('--log-file', help='Log file', default='gitlab2gitea.log') parser.add_argument('--append-log', help='Append log file', action='store_true') args = parser.parse_args() if args.gitlab_api_version: GITLAB_API_VERSION = args.gitlab_api_version if args.gitlab_token: GITLAB_TOKEN = args.gitlab_token if args.gitlab_url: GITLAB_URL = args.gitlab_url if args.gitea_token: GITEA_TOKEN = args.gitea_token if args.gitea_url: GITEA_URL = args.gitea_url if args.gitea_api_version: GITEA_API_VERSION = args.gitea_api_version if args.quiet: QUIET = True if args.debug: DEBUG = True if args.dry_run: DRY_RUN = True if args.log_file: LOG_FILE = args.log_file if args.append_log: APPEND_LOG = True # Remove trailing slashes from URLs GITLAB_URL = GITLAB_URL.rstrip('/') GITEA_URL = GITEA_URL.rstrip('/') # Internal functions def _debug(message): if DEBUG: print('\033[1m\033[34m[DBG]\033[0m', message) if LOG_FILE: with open(LOG_FILE, 'a') as log_file: log_file.write(f'[DBG] {message}\n') def _info(message): print('\033[1m\033[32m[INF]\033[0m', message) if LOG_FILE: with open(LOG_FILE, 'a') as log_file: log_file.write(f'[INF] {message}\n') def _warn(message): print('\033[1m\033[33m[WRN]\033[0m', message) if LOG_FILE: with open(LOG_FILE, 'a') as log_file: log_file.write(f'[WRN] {message}\n') def _error(message): print('\033[1m\033[31m[ERR]\033[0m', message) if LOG_FILE: with open(LOG_FILE, 'a') as log_file: log_file.write(f'[ERR] {message}\n') # PROGRAM # Endpoint: GET /api/{GITLAB_API_VERSION}/version def check_gitlab(): _debug(f'REQUEST: {GITLAB_URL}/api/{GITLAB_API_VERSION}/version') response = requests.get(f'{GITLAB_URL}/api/{GITLAB_API_VERSION}/version', headers={ 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'Bearer {GITLAB_TOKEN}' }) if response.status_code != 200: response_message = response.json()['message'] if 'message' in response.json() else 'Unknown error' raise Exception(f'GitLab endpoint test failed: {response_message}') else: _info(f'GitLab endpoint version: {response.json()["version"]}') # Endpoint: GET /api/{GITEA_API_VERSION}/version def check_gitea(): _debug(f'REQUEST: {GITEA_URL}/api/{GITEA_API_VERSION}/version') response = requests.get(f'{GITEA_URL}/api/{GITEA_API_VERSION}/version', headers={ 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'token {GITEA_TOKEN}' }) if response.status_code != 200: response_message = response.json()['message'] if 'message' in response.json() else 'Unknown error' raise Exception(f'Gitea endpoint test failed: {response_message}') else: _info(f'Gitea endpoint version: {response.json()["version"]}') # Endpoint: GET /api/{GITLAB_API_VERSION}/groups def get_gitlab_groups() -> list: pass # Endpoint: GET /api/{GITLAB_API_VERSION}/users def get_gitlab_users() -> list: pass # Endpoint: GET /api/{GITLAB_API_VERSION}/projects def get_gitlab_projects() -> list: pass # Endpoint: GET /api/{GITEA_API_VERSION}/orgs def get_gitea_groups() -> list: pass # Endpoint: GET /api/{GITEA_API_VERSION}/users def get_gitea_users() -> list: pass # Endpoint: GET /api/{GITEA_API_VERSION}/repos def get_gitea_projects() -> list: pass def cmp_gitlab_gitea_groups(gitlab_groups: list, gitea_groups: list) -> dict: pass def cmp_gitlab_gitea_users(gitlab_users: list, gitea_users: list) -> dict: pass def cmp_gitlab_gitea_projects(gitlab_projects: list, gitea_projects: list) -> dict: pass def run_migration(): _info('Migrating GitLab groups...') gitlab_groups = get_gitlab_groups() gitea_groups = get_gitea_groups() cmp_gitlab_gitea_groups(gitlab_groups, gitea_groups) _info('Migrating GitLab users...') gitlab_users = get_gitlab_users() gitea_users = get_gitea_users() cmp_gitlab_gitea_users(gitlab_users, gitea_users) _info('Migrating GitLab projects...') gitlab_projects = get_gitlab_projects() gitea_projects = get_gitea_projects() cmp_gitlab_gitea_projects(gitlab_projects, gitea_projects) def main(): if DRY_RUN: _warn('Running in dry-run mode!') if not GITLAB_TOKEN: _error('GitLab access token is missing!') return if not GITEA_TOKEN: _error('Gitea access token is missing!') return if LOG_FILE and not APPEND_LOG: with open(LOG_FILE, 'w') as log_file: log_file.write('') _debug(f'GitLab URL: {GITLAB_URL}') _debug(f'Gitea URL: {GITEA_URL}') _debug(f'Logging to: {LOG_FILE}') _info('Testing endpoints...') try: check_gitlab() check_gitea() except Exception as e: _error(f'An error occurred: {e}') return _info('Starting migration...') try: run_migration() except Exception as e: _error(f'An error occurred: {e}') if __name__ == '__main__': main()