import os import subprocess import requests import logging from logging.handlers import RotatingFileHandler logger = logging.getLogger("gh-mirror.py") logfile = os.path.join(os.path.dirname(__file__), "logs", "gh-mirror.log") logdir = os.path.dirname(logfile) if not os.path.exists(logdir): os.makedirs(logdir) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(), RotatingFileHandler(filename=logfile, maxBytes=512000, backupCount=4) ] ) def load_dotenv(dotenv_file='.env'): try: with open(dotenv_file) as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue key, value = line.split('=', 1) os.environ[key] = value except FileNotFoundError: logging.error(f"Error: '{dotenv_file}' file not found.") def run_command(command): try: return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True, universal_newlines=True) except subprocess.CalledProcessError as e: logging.error(f"Command '{e.cmd}' returned non-zero exit status {e.returncode}. Output:\n{e.output}") def get_github_repos(github_username, github_token): """Fetch a list of all GitHub repositories for the authenticated user with pagination.""" repos = [] page = 1 while True: url = f"https://api.github.com/user/repos?visibility=all&per_page=100&page={page}" headers = {'Authorization': f'token {github_token}'} response = requests.get(url, headers=headers) if response.status_code == 200: page_repos = response.json() if not page_repos: break # No more repositories to fetch repos.extend([repo for repo in page_repos if repo['owner']['login'] == github_username]) page += 1 else: logging.error(f"Failed to fetch GitHub repos (HTTP status: {response.status_code}).") break return repos def create_gitlab_repo(gitlab_token, repo_name, gitlab_group_id): headers = {'PRIVATE-TOKEN': gitlab_token} data = {'name': repo_name, 'namespace_id': gitlab_group_id} response = requests.post('https://gitlab.com/api/v4/projects', headers=headers, data=data) return response.status_code def mirror_repo_to_gitlab(github_username, repo_name, gitlab_group_name): run_command(f'git clone --mirror git@github.com:{github_username}/{repo_name}.git {repo_name}') os.chdir(repo_name) run_command(f'git remote add gitlab git@gitlab.com:{gitlab_group_name}/{repo_name}.git') run_command('git config --local --replace remote.origin.fetch "+refs/heads/*:refs/heads/*"') run_command('git config --local --add remote.origin.fetch "+refs/tags/*:refs/tags/*"') run_command('git config --local --add remote.origin.fetch "+refs/change/*:refs/change/*"') run_command('git config --local --replace remote.gitlab.push "+refs/heads/*:refs/heads/*"') run_command('git config --local --add remote.gitlab.push "+refs/tags/*:refs/tags/*"') run_command('git config --local --add remote.gitlab.push "+refs/change/*:refs/change/*"') run_command('git push --mirror gitlab') os.chdir('..') run_command(f'rm -rf {repo_name}') def main(): load_dotenv() github_username = os.environ.get('GITHUB_USERNAME', 'Github username not set') # gitlab_username = os.environ.get('GITLAB_USERNAME', 'GitLab username not set') github_token = os.environ.get('GITHUB_TOKEN', 'GitHub token not set') gitlab_token = os.environ.get('GITLAB_TOKEN', 'GitLab token not set') gitlab_group_id = os.environ.get('GITLAB_GROUP_ID', 'GitLab group ID not set') gitlab_group_name = os.environ.get('GITLAB_GROUP_NAME', 'GitLab group name not') repos = get_github_repos(github_username, github_token) logging.info(f"Found {len(repos)} repositories on GitHub") for repo in repos: repo_name = repo['name'] logging.info(f"Mirroring {repo_name}") status = create_gitlab_repo(gitlab_token, repo_name, gitlab_group_id) if status == 201: logging.debug(f"Repository {repo_name} created on GitLab") elif status == 400: logging.debug(f"Repository {repo_name} already exists on GitLab") else: logging.error(f"An unexpected error occurred while creating {repo_name} on GitLab (HTTP status: {status}).") continue mirror_repo_to_gitlab(github_username, repo_name, gitlab_group_name) if __name__ == '__main__': main()