You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

161 lines
6.0 KiB

"""
Main migration tool for transferring repositories from Gitea to GitHub
"""
import os
import subprocess
import tempfile
import shutil
import logging
from typing import List, Dict, Optional
from pathlib import Path
from config import Config
from gitea_client import GiteaClient
from github_client import GitHubClient
logger = logging.getLogger(__name__)
class MigrationTool:
"""Main tool for migrating repositories from Gitea to GitHub"""
def __init__(self, config: Config):
self.config = config
self.gitea_client = GiteaClient(
config.gitea_url,
config.gitea_token,
config.gitea_username
)
self.github_client = GitHubClient(
config.github_token,
config.github_username
)
def migrate_all_user_repos(self) -> Dict[str, bool]:
"""Migrate all repositories owned by the user"""
logger.info("Fetching user repositories from Gitea...")
repos = self.gitea_client.get_user_repos()
results = {}
for repo in repos:
if repo['owner']['login'] == self.config.gitea_username:
repo_name = repo['name']
logger.info(f"Migrating repository: {repo_name}")
success = self.migrate_repository(repo)
results[repo_name] = success
return results
def migrate_specific_repos(self, repo_specs: List[str]) -> Dict[str, bool]:
"""
Migrate specific repositories
repo_specs: List of repository specifications in format 'owner/repo' or just 'repo'
"""
results = {}
for repo_spec in repo_specs:
if '/' in repo_spec:
owner, repo_name = repo_spec.split('/', 1)
else:
owner = self.config.gitea_username
repo_name = repo_spec
logger.info(f"Migrating repository: {owner}/{repo_name}")
repo_info = self.gitea_client.get_repo_info(owner, repo_name)
if repo_info:
success = self.migrate_repository(repo_info)
results[f"{owner}/{repo_name}"] = success
else:
logger.error(f"Repository {owner}/{repo_name} not found or not accessible")
results[f"{owner}/{repo_name}"] = False
return results
def migrate_repository(self, repo_info: Dict) -> bool:
"""Migrate a single repository"""
repo_name = repo_info['name']
repo_owner = repo_info['owner']['login']
try:
# Create GitHub repository
success = self.github_client.create_repository(
repo_name=repo_name,
description=repo_info.get('description', ''),
private=repo_info.get('private', False)
)
if not success:
return False
# Clone and push repository
return self._clone_and_push_repo(repo_owner, repo_name)
except Exception as e:
logger.error(f"Failed to migrate repository {repo_name}: {e}")
return False
def _clone_and_push_repo(self, repo_owner: str, repo_name: str) -> bool:
"""Clone repository from Gitea and push to GitHub"""
temp_dir = None
try:
# Create temporary directory
temp_dir = tempfile.mkdtemp(prefix=f"migration_{repo_name}_")
repo_path = Path(temp_dir) / repo_name
# Clone from Gitea
gitea_url = self._get_authenticated_gitea_url(repo_owner, repo_name)
clone_cmd = ['git', 'clone', '--mirror', gitea_url, str(repo_path)]
logger.info(f"Cloning repository from Gitea: {repo_owner}/{repo_name}")
result = subprocess.run(clone_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to clone repository: {result.stderr}")
return False
# Change to repository directory
os.chdir(repo_path)
# Add GitHub remote
github_url = self.github_client.get_authenticated_clone_url(
repo_name,
self.config.github_token
)
add_remote_cmd = ['git', 'remote', 'add', 'github', github_url]
result = subprocess.run(add_remote_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to add GitHub remote: {result.stderr}")
return False
# Push to GitHub
logger.info(f"Pushing repository to GitHub: {repo_name}")
push_cmd = ['git', 'push', '--mirror', 'github']
result = subprocess.run(push_cmd, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to push to GitHub: {result.stderr}")
return False
logger.info(f"Successfully migrated repository: {repo_name}")
return True
except Exception as e:
logger.error(f"Error during repository migration: {e}")
return False
finally:
# Clean up temporary directory
if temp_dir and os.path.exists(temp_dir):
shutil.rmtree(temp_dir, ignore_errors=True)
def _get_authenticated_gitea_url(self, owner: str, repo_name: str) -> str:
"""Get authenticated Gitea URL for cloning"""
base_url = self.config.gitea_url.replace('https://', '').replace('http://', '')
return f"https://{self.config.gitea_username}:{self.config.gitea_token}@{base_url}/{owner}/{repo_name}.git"
def list_available_repos(self) -> List[Dict]:
"""List all repositories available for migration"""
return self.gitea_client.list_accessible_repos()