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
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() |