Final version

main
Hugo ODY 3 days ago
parent 8fdcdc23b9
commit 957ebaaff0

@ -40,48 +40,39 @@ Cela créera un fichier `.env` que vous devrez remplir avec vos informations sel
## 🔧 Configuration
### Configuration basique (Gitea → GitHub)
### Configuration avec support multi-instances
```env
# Source Provider
SOURCE_PROVIDER=gitea
GITEA_URL=https://votre-instance-gitea.com
GITEA_TOKEN=votre_token_gitea
GITEA_USERNAME=votre_nom_utilisateur_gitea
# Destination Provider
DESTINATION_PROVIDER=github
GITHUB_TOKEN=votre_token_github
GITHUB_USERNAME=votre_nom_utilisateur_github
```
# Gitea Source Configuration
GITEA_SOURCE_URL=https://votre-instance-gitea-source.com
GITEA_SOURCE_TOKEN=votre_token_gitea_source
GITEA_SOURCE_USERNAME=votre_nom_utilisateur_gitea_source
# Gitea Destination Configuration
GITEA_DEST_URL=https://votre-instance-gitea-dest.com
GITEA_DEST_TOKEN=votre_token_gitea_dest
GITEA_DEST_USERNAME=votre_nom_utilisateur_gitea_dest
# GitLab Source Configuration
GITLAB_SOURCE_URL=https://gitlab-source.com
GITLAB_SOURCE_TOKEN=votre_token_gitlab_source
GITLAB_SOURCE_USERNAME=votre_nom_utilisateur_gitlab_source
# GitLab Destination Configuration
GITLAB_DEST_URL=https://gitlab-dest.com
GITLAB_DEST_TOKEN=votre_token_gitlab_dest
GITLAB_DEST_USERNAME=votre_nom_utilisateur_gitlab_dest
### Configuration GitLab → GitHub
```env
# Source Provider
SOURCE_PROVIDER=gitlab
GITLAB_URL=https://gitlab.com
GITLAB_TOKEN=votre_token_gitlab
GITLAB_USERNAME=votre_nom_utilisateur_gitlab
# Destination Provider
DESTINATION_PROVIDER=github
# GitHub Configuration (same for source and destination - only one instance)
GITHUB_TOKEN=votre_token_github
GITHUB_USERNAME=votre_nom_utilisateur_github
```
### Configuration GitLab → GitLab (migration entre instances)
```env
# Source Provider
SOURCE_PROVIDER=gitlab
GITLAB_URL=https://gitlab-source.com
GITLAB_TOKEN=votre_token_gitlab_source
GITLAB_USERNAME=votre_nom_utilisateur_source
# Destination Provider
DESTINATION_PROVIDER=gitlab
GITLAB_DEST_URL=https://gitlab-destination.com
GITLAB_DEST_TOKEN=votre_token_gitlab_dest
GITLAB_DEST_USERNAME=votre_nom_utilisateur_dest
```
**📝 Instructions :**
1. **Multi-instances** : Vous pouvez configurer différentes instances du même provider
2. **Même instance** : Utilisez les mêmes credentials pour source et destination si c'est la même instance
3. **Migration flexible** : Supports GitLab → GitLab, Gitea → Gitea, etc. entre différentes instances
4. **Configuration minimale** : Configurez seulement les providers source/destination que vous utilisez
5. L'outil vous demandera interactivement quel provider utiliser comme source et destination
## 🔑 Configuration des tokens
@ -173,33 +164,29 @@ Après la sélection, l'outil propose de renommer les repositories :
## 📋 Exemples d'utilisation
### Exemple 1 : Migration Gitea → GitHub (défaut)
### Exemple 1 : Migration interactive (défaut)
```bash
# Configuration dans .env
SOURCE_PROVIDER=gitea
DESTINATION_PROVIDER=github
# Interface interactive pour sélectionner les repos
# 1. Configurez vos providers dans .env
# 2. Lancez l'outil
./run.sh
# L'outil vous demandera :
# - Quel provider utiliser comme source
# - Quel provider utiliser comme destination
# - Puis vous pourrez sélectionner les repos à migrer
```
### Exemple 2 : Migration GitLab → GitHub
### Exemple 2 : Migration automatique
```bash
# Configuration dans .env
SOURCE_PROVIDER=gitlab
DESTINATION_PROVIDER=github
# Migration automatique
# Migre tous vos repositories automatiquement
# (après sélection interactive des providers)
./run.sh --no-interactive
```
### Exemple 3 : Migration GitLab → GitLab (entre instances)
### Exemple 3 : Migration sélective
```bash
# Configuration dans .env
SOURCE_PROVIDER=gitlab
DESTINATION_PROVIDER=gitlab
# Migration sélective
# Migre seulement les repositories spécifiés
# (après sélection interactive des providers)
./run.sh --repos projet-web api-backend
```
@ -211,17 +198,17 @@ DESTINATION_PROVIDER=gitlab
### Exemple 5 : Premier lancement (configuration)
```bash
# 1. Setup initial
# 1. Setup initial - crée le fichier .env template
./run.sh --setup
# 2. Éditez le fichier .env avec vos credentials et providers
# 2. Éditez le fichier .env avec vos credentials (au moins 2 providers)
nano .env
# 3. Listez vos repositories disponibles
./run.sh --list
# 4. Lancez la migration interactive
# 3. Lancez l'outil - il vous demandera quels providers utiliser
./run.sh
# 4. Pour lister les repos disponibles (après sélection du provider source)
./run.sh --list
```
### Exemple 6 : Migration avec renommage
@ -229,12 +216,13 @@ nano .env
# 1. Lancer le mode interactif
./run.sh
# 2. Sélectionner les repos à migrer
# 3. Choisir "Y" pour le renommage
# 4. Renommer les repos un par un
# 2. Sélectionner les providers source et destination
# 3. Sélectionner les repos à migrer
# 4. Choisir "Y" pour le renommage
# 5. Renommer les repos un par un
# - Appuyer sur ENTRÉE pour garder le nom original
# - Taper un nouveau nom pour renommer
# 5. Confirmer et lancer la migration
# 6. Confirmer et lancer la migration
```
## 📊 Résultats
@ -314,17 +302,20 @@ GitMigrator/
- L'outil vérifie automatiquement l'existence sur le provider de destination
- Les repositories existants sont ignorés avec un avertissement
### Provider non supporté
- Vérifiez que le provider est bien configuré dans SOURCE_PROVIDER ou DESTINATION_PROVIDER
- Providers disponibles : gitea, gitlab (source) | github, gitlab (destination)
### Provider non supporté ou non configuré
- Vérifiez que vos providers sont bien configurés dans le fichier .env
- Assurez-vous d'avoir au moins 2 providers configurés
- Providers disponibles : gitea, gitlab, github
- L'outil vous indiquera quels providers sont configurés au démarrage
## 📝 Logs
Tous les détails d'exécution sont sauvegardés dans `migration.log` :
- Timestamps des opérations
- Sélection des providers source et destination
- Détails des erreurs
- Statistiques de migration
- Informations sur les providers utilisés
- Informations complètes sur le processus de migration
## 🚀 Extensibilité

@ -2,75 +2,149 @@
Configuration management for migration tool
"""
import os
from typing import Dict, Any
from typing import Dict, Any, Optional
from dotenv import load_dotenv
from providers.base import ConfigurationError
# Load environment variables from .env file
load_dotenv()
class MigrationConfig:
"""Configuration manager for migration settings"""
def __init__(self):
self.source_provider = os.getenv('SOURCE_PROVIDER', 'gitea').lower()
self.destination_provider = os.getenv('DESTINATION_PROVIDER', 'github').lower()
self.source_config = self._load_source_config()
self.destination_config = self._load_destination_config()
# Reload environment variables from .env file each time
load_dotenv(override=True)
self._validate_config()
def _load_source_config(self) -> Dict[str, Any]:
"""Load source provider configuration"""
if self.source_provider == 'gitea':
return {
'url': os.getenv('GITEA_URL', 'https://codefirst.iut.uca.fr/git'),
'token': os.getenv('GITEA_TOKEN'),
'username': os.getenv('GITEA_USERNAME')
}
elif self.source_provider == 'gitlab':
return {
'url': os.getenv('GITLAB_URL', 'https://gitlab.com'),
'token': os.getenv('GITLAB_TOKEN'),
'username': os.getenv('GITLAB_USERNAME')
}
# Load all provider configurations (source and destination)
self.gitea_source_config = self._load_gitea_source_config()
self.gitea_dest_config = self._load_gitea_dest_config()
self.gitlab_source_config = self._load_gitlab_source_config()
self.gitlab_dest_config = self._load_gitlab_dest_config()
self.github_config = self._load_github_config() # Single config for GitHub
def _load_gitea_source_config(self) -> Dict[str, Any]:
"""Load Gitea source configuration"""
return {
'url': os.getenv('GITEA_SOURCE_URL', 'https://codefirst.iut.uca.fr/git'),
'token': os.getenv('GITEA_SOURCE_TOKEN'),
'username': os.getenv('GITEA_SOURCE_USERNAME')
}
def _load_gitea_dest_config(self) -> Dict[str, Any]:
"""Load Gitea destination configuration"""
return {
'url': os.getenv('GITEA_DEST_URL', 'https://codefirst.iut.uca.fr/git'),
'token': os.getenv('GITEA_DEST_TOKEN'),
'username': os.getenv('GITEA_DEST_USERNAME')
}
def _load_gitlab_source_config(self) -> Dict[str, Any]:
"""Load GitLab source configuration"""
return {
'url': os.getenv('GITLAB_SOURCE_URL', 'https://gitlab.com'),
'token': os.getenv('GITLAB_SOURCE_TOKEN'),
'username': os.getenv('GITLAB_SOURCE_USERNAME')
}
def _load_gitlab_dest_config(self) -> Dict[str, Any]:
"""Load GitLab destination configuration"""
return {
'url': os.getenv('GITLAB_DEST_URL', 'https://gitlab.com'),
'token': os.getenv('GITLAB_DEST_TOKEN'),
'username': os.getenv('GITLAB_DEST_USERNAME')
}
def _load_github_config(self) -> Dict[str, Any]:
"""Load GitHub configuration"""
return {
'token': os.getenv('GITHUB_TOKEN'),
'username': os.getenv('GITHUB_USERNAME')
}
def get_source_provider_config(self, provider_type: str) -> Dict[str, Any]:
"""Get source configuration for a specific provider"""
if provider_type == 'gitea':
return self.gitea_source_config
elif provider_type == 'gitlab':
return self.gitlab_source_config
elif provider_type == 'github':
return self.github_config # Same config for source and dest
else:
raise ConfigurationError(f"Unsupported source provider: {self.source_provider}")
def _load_destination_config(self) -> Dict[str, Any]:
"""Load destination provider configuration"""
if self.destination_provider == 'github':
return {
'token': os.getenv('GITHUB_TOKEN'),
'username': os.getenv('GITHUB_USERNAME')
}
elif self.destination_provider == 'gitlab':
return {
'url': os.getenv('GITLAB_DEST_URL', 'https://gitlab.com'),
'token': os.getenv('GITLAB_DEST_TOKEN'),
'username': os.getenv('GITLAB_DEST_USERNAME')
}
raise ConfigurationError(f"Unknown source provider type: {provider_type}")
def get_destination_provider_config(self, provider_type: str) -> Dict[str, Any]:
"""Get destination configuration for a specific provider"""
if provider_type == 'gitea':
return self.gitea_dest_config
elif provider_type == 'gitlab':
return self.gitlab_dest_config
elif provider_type == 'github':
return self.github_config # Same config for source and dest
else:
raise ConfigurationError(f"Unsupported destination provider: {self.destination_provider}")
def _validate_config(self) -> None:
"""Validate configuration completeness"""
# Check source config
missing_source = [key for key, value in self.source_config.items() if not value]
if missing_source:
raise ConfigurationError(f"Missing {self.source_provider} source configuration: {', '.join(missing_source)}")
# Check destination config
missing_dest = [key for key, value in self.destination_config.items() if not value]
if missing_dest:
raise ConfigurationError(f"Missing {self.destination_provider} destination configuration: {', '.join(missing_dest)}")
raise ConfigurationError(f"Unknown destination provider type: {provider_type}")
def is_valid(self) -> bool:
"""Check if configuration is valid"""
def is_source_provider_configured(self, provider_type: str) -> bool:
"""Check if a source provider is configured (has all required fields)"""
try:
self._validate_config()
return True
config = self.get_source_provider_config(provider_type)
return all(value for value in config.values())
except ConfigurationError:
return False
return False
def is_destination_provider_configured(self, provider_type: str) -> bool:
"""Check if a destination provider is configured (has all required fields)"""
try:
config = self.get_destination_provider_config(provider_type)
return all(value for value in config.values())
except ConfigurationError:
return False
def get_available_source_providers(self) -> Dict[str, bool]:
"""Get list of source providers and their configuration status"""
return {
'gitea': self.is_source_provider_configured('gitea'),
'gitlab': self.is_source_provider_configured('gitlab'),
'github': self.is_source_provider_configured('github')
}
def get_available_destination_providers(self) -> Dict[str, bool]:
"""Get list of destination providers and their configuration status"""
return {
'gitea': self.is_destination_provider_configured('gitea'),
'gitlab': self.is_destination_provider_configured('gitlab'),
'github': self.is_destination_provider_configured('github')
}
def validate_source_provider_config(self, provider_type: str) -> None:
"""Validate source configuration for a specific provider"""
config = self.get_source_provider_config(provider_type)
missing = [key for key, value in config.items() if not value]
if missing:
raise ConfigurationError(f"Missing {provider_type} source configuration: {', '.join(missing)}")
def validate_destination_provider_config(self, provider_type: str) -> None:
"""Validate destination configuration for a specific provider"""
config = self.get_destination_provider_config(provider_type)
missing = [key for key, value in config.items() if not value]
if missing:
raise ConfigurationError(f"Missing {provider_type} destination configuration: {', '.join(missing)}")
def is_valid(self) -> bool:
"""Check if at least one source and one destination provider are configured"""
source_configured = any(self.get_available_source_providers().values())
dest_configured = any(self.get_available_destination_providers().values())
return source_configured and dest_configured
# Méthodes dépréciées pour compatibilité (si jamais utilisées ailleurs)
def get_provider_config(self, provider_type: str) -> Dict[str, Any]:
"""DEPRECATED: Use get_source_provider_config or get_destination_provider_config"""
return self.get_source_provider_config(provider_type)
def is_provider_configured(self, provider_type: str) -> bool:
"""DEPRECATED: Use is_source_provider_configured or is_destination_provider_configured"""
return self.is_source_provider_configured(provider_type)
def get_available_providers(self) -> Dict[str, bool]:
"""DEPRECATED: Use get_available_source_providers or get_available_destination_providers"""
return self.get_available_source_providers()

@ -4,8 +4,8 @@ Repository Migration Tool
A flexible tool for migrating repositories between different Git hosting providers.
Currently supports:
- Source providers: Gitea
- Destination providers: GitHub
- Source providers: Gitea, GitLab
- Destination providers: GitHub, GitLab
Future providers can be easily added through the extensible provider system.
"""
@ -21,6 +21,7 @@ from core.migration_engine import MigrationEngine
from providers.factory import ProviderFactory
from providers.base import ConfigurationError, ProviderError, MigrationError
from ui.interactive_selector import select_repositories_interactive
from ui.provider_selector import select_providers
# Initialize colorama for cross-platform colored output
init()
@ -41,21 +42,15 @@ def setup_logging(verbose: bool = False):
def print_banner():
"""Print application banner"""
banner = f"""
{Fore.CYAN}
🚀 Repository Migration Tool 🚀
Migrate repositories between Git hosting providers
{Style.RESET_ALL}
"""
print(banner)
print(f"{Fore.MAGENTA}{'='*60}")
print(f"{'🚀 GIT MIGRATION TOOL':^60}")
print(f"{'Multi-Provider Repository Migration':^60}")
print(f"{'='*60}{Style.RESET_ALL}")
def print_success_summary(results: dict):
"""Print migration results summary"""
successful = sum(1 for success in results.values() if success)
total = len(results)
successful = sum(1 for success in results.values() if success)
print(f"\n{Fore.GREEN}{'='*60}")
print(f" MIGRATION SUMMARY")
@ -75,28 +70,36 @@ def create_env_template():
env_file = Path('.env')
if not env_file.exists():
template = """# Source Provider Configuration
SOURCE_PROVIDER=gitea
GITEA_URL=https://codefirst.iut.uca.fr/git
GITEA_TOKEN=your_gitea_personal_access_token
GITEA_USERNAME=your_gitea_username
template = """# Gitea Source Configuration
GITEA_SOURCE_URL=https://codefirst.iut.uca.fr/git
GITEA_SOURCE_TOKEN=your_gitea_source_personal_access_token
GITEA_SOURCE_USERNAME=your_gitea_source_username
# Alternative source provider (GitLab)
# SOURCE_PROVIDER=gitlab
# GITLAB_URL=https://gitlab.com
# GITLAB_TOKEN=your_gitlab_token
# GITLAB_USERNAME=your_gitlab_username
# Gitea Destination Configuration
GITEA_DEST_URL=https://codefirst.iut.uca.fr/git
GITEA_DEST_TOKEN=your_gitea_dest_personal_access_token
GITEA_DEST_USERNAME=your_gitea_dest_username
# Destination Provider Configuration
DESTINATION_PROVIDER=github
# GitLab Source Configuration
GITLAB_SOURCE_URL=https://gitlab.com
GITLAB_SOURCE_TOKEN=your_gitlab_source_token
GITLAB_SOURCE_USERNAME=your_gitlab_source_username
# GitLab Destination Configuration
GITLAB_DEST_URL=https://gitlab.com
GITLAB_DEST_TOKEN=your_gitlab_dest_token
GITLAB_DEST_USERNAME=your_gitlab_dest_username
# GitHub Configuration (same for source and destination - only one instance)
GITHUB_TOKEN=your_github_personal_access_token
GITHUB_USERNAME=your_github_username
# Alternative destination provider (GitLab)
# DESTINATION_PROVIDER=gitlab
# GITLAB_DEST_URL=https://gitlab.com
# GITLAB_DEST_TOKEN=your_gitlab_dest_token
# GITLAB_DEST_USERNAME=your_gitlab_dest_username
# Instructions:
# 1. Fill in the credentials for the providers you want to use as source or destination
# 2. You can use the same credentials for source and dest if it's the same instance
# 3. For migrations between different instances of the same provider, use different credentials
# 4. GitHub only has one instance (github.com), so GitHub→GitHub migrations are not supported
# 5. The tool will ask you which provider to use as source and destination
"""
env_file.write_text(template)
print(f"{Fore.YELLOW}📝 Created .env template file. Please fill it with your credentials.{Style.RESET_ALL}")
@ -172,14 +175,17 @@ Supported providers:
# Initialize configuration
config = MigrationConfig()
# Select providers interactively
source_provider_type, destination_provider_type = select_providers()
# Create providers
source_provider = ProviderFactory.create_source_provider(
config.source_provider,
config.source_config
source_provider_type,
config.get_source_provider_config(source_provider_type)
)
destination_provider = ProviderFactory.create_destination_provider(
config.destination_provider,
config.destination_config
destination_provider_type,
config.get_destination_provider_config(destination_provider_type)
)
# Initialize migration engine
@ -187,7 +193,7 @@ Supported providers:
# Handle list command
if args.list:
print(f"{Fore.CYAN}📋 Available repositories from {config.source_provider}:{Style.RESET_ALL}")
print(f"{Fore.CYAN}📋 Available repositories from {source_provider_type}:{Style.RESET_ALL}")
repos = source_provider.get_accessible_repositories()
for repo in repos:
@ -209,7 +215,7 @@ Supported providers:
if '/' in repo_spec:
owner, repo_name = repo_spec.split('/', 1)
else:
owner = config.source_config['username']
owner = config.get_source_provider_config(source_provider_type)['username']
repo_name = repo_spec
repo = source_provider.get_repository_info(owner, repo_name)
@ -230,7 +236,7 @@ Supported providers:
results = migration_engine.migrate_repositories(user_repos)
else:
print(f"{Fore.CYAN}🎯 Interactive mode - select repositories to migrate{Style.RESET_ALL}")
username = config.source_config['username']
username = config.get_source_provider_config(source_provider_type)['username']
selected_repos = select_repositories_interactive(all_repos, username)
results = migration_engine.migrate_repositories(selected_repos)

@ -0,0 +1,92 @@
"""
Gitea destination provider implementation
"""
import logging
import requests
from typing import Dict
from ..base import DestinationProvider, Repository, ProviderError, ConfigurationError
logger = logging.getLogger(__name__)
class GiteaDestinationProvider(DestinationProvider):
"""Gitea destination provider implementation"""
def _validate_config(self) -> None:
"""Validate Gitea-specific configuration"""
required_keys = ['url', 'token', 'username']
missing = [key for key in required_keys if not self.config.get(key)]
if missing:
raise ConfigurationError(f"Missing Gitea configuration: {', '.join(missing)}")
self.base_url = self.config['url'].rstrip('/')
self.token = self.config['token']
self.username = self.config['username']
# Setup HTTP session
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'token {self.token}',
'Content-Type': 'application/json'
})
# Verify authentication
try:
response = self.session.get(f"{self.base_url}/api/v1/user")
response.raise_for_status()
except requests.RequestException as e:
raise ConfigurationError(f"Failed to authenticate with Gitea: {e}")
def create_repository(self, repository: Repository, target_name: str) -> bool:
"""Create a new repository on Gitea"""
response = None
try:
# Check if repository already exists
if self.repository_exists(target_name):
logger.warning(f"Repository {target_name} already exists on Gitea")
return True
repo_data = {
'name': target_name,
'description': repository.description or '',
'private': repository.private,
'auto_init': False # Don't auto-init since we'll push existing content
}
response = self.session.post(
f"{self.base_url}/api/v1/user/repos",
json=repo_data
)
response.raise_for_status()
logger.info(f"Created repository: {target_name}")
return True
except requests.RequestException as e:
logger.error(f"Failed to create repository {target_name}: {e}")
if response and response.status_code == 409:
# Conflict - repository already exists
logger.warning(f"Repository {target_name} already exists (conflict)")
return True
elif response and response.status_code == 422:
# Unprocessable Entity - might already exist or name is invalid
logger.warning(f"Repository creation failed, possibly already exists: {target_name}")
return self.repository_exists(target_name)
raise ProviderError(f"Failed to create Gitea repository: {e}")
except Exception as e:
logger.error(f"Unexpected error creating repository {target_name}: {e}")
return False
def repository_exists(self, name: str) -> bool:
"""Check if a repository exists"""
try:
response = self.session.get(f"{self.base_url}/api/v1/repos/{self.username}/{name}")
return response.status_code == 200
except requests.RequestException:
return False
def get_authenticated_push_url(self, name: str) -> str:
"""Get authenticated URL for pushing to repository"""
base_url = self.base_url.replace('https://', '').replace('http://', '')
return f"https://{self.username}:{self.token}@{base_url}/{self.username}/{name}.git"

@ -5,6 +5,8 @@ from typing import Dict, Type
from .base import SourceProvider, DestinationProvider, ConfigurationError
from .source.gitea import GiteaSourceProvider
from .source.gitlab import GitLabSourceProvider
from .source.github import GitHubSourceProvider
from .destination.gitea import GiteaDestinationProvider
from .destination.github import GitHubDestinationProvider
from .destination.gitlab import GitLabDestinationProvider
@ -15,9 +17,11 @@ class ProviderFactory:
_source_providers: Dict[str, Type[SourceProvider]] = {
'gitea': GiteaSourceProvider,
'gitlab': GitLabSourceProvider,
'github': GitHubSourceProvider,
}
_destination_providers: Dict[str, Type[DestinationProvider]] = {
'gitea': GiteaDestinationProvider,
'github': GitHubDestinationProvider,
'gitlab': GitLabDestinationProvider,
}

@ -0,0 +1,84 @@
"""
GitHub source provider implementation
"""
import logging
from github import Github
from github.GithubException import GithubException
from typing import List, Dict, Optional
from ..base import SourceProvider, Repository, ProviderError, ConfigurationError
logger = logging.getLogger(__name__)
class GitHubSourceProvider(SourceProvider):
"""GitHub source provider implementation"""
def _validate_config(self) -> None:
"""Validate GitHub-specific configuration"""
required_keys = ['token', 'username']
missing = [key for key in required_keys if not self.config.get(key)]
if missing:
raise ConfigurationError(f"Missing GitHub configuration: {', '.join(missing)}")
self.token = self.config['token']
self.username = self.config['username']
try:
self.github = Github(self.token)
self.user = self.github.get_user()
except GithubException as e:
raise ConfigurationError(f"Failed to authenticate with GitHub: {e}")
def get_user_repositories(self) -> List[Repository]:
"""Get repositories owned by the authenticated user"""
repositories = self.get_accessible_repositories()
return [repo for repo in repositories if repo.owner == self.username]
def get_accessible_repositories(self) -> List[Repository]:
"""Get all repositories accessible to the authenticated user"""
all_repos = []
try:
# Get user's own repositories
user_repos = self.user.get_repos()
all_repos.extend([self._parse_repository(repo) for repo in user_repos])
# Get repositories from organizations the user belongs to
for org in self.user.get_orgs():
try:
org_repos = org.get_repos()
all_repos.extend([self._parse_repository(repo) for repo in org_repos])
except GithubException as e:
logger.warning(f"Could not fetch repositories from organization {org.login}: {e}")
continue
except GithubException as e:
raise ProviderError(f"Failed to fetch repositories from GitHub: {e}")
return all_repos
def get_repository_info(self, owner: str, name: str) -> Optional[Repository]:
"""Get information about a specific repository"""
try:
repo = self.github.get_repo(f"{owner}/{name}")
return self._parse_repository(repo)
except GithubException:
return None
def get_authenticated_clone_url(self, repository: Repository) -> str:
"""Get authenticated clone URL for a repository"""
return f"https://{self.token}@github.com/{repository.owner}/{repository.name}.git"
def _parse_repository(self, repo) -> Repository:
"""Parse repository data from GitHub API response"""
return Repository(
name=repo.name,
owner=repo.owner.login,
description=repo.description or '',
private=repo.private,
clone_url=repo.clone_url,
ssh_url=repo.ssh_url,
web_url=repo.html_url,
default_branch=repo.default_branch or 'main'
)

@ -0,0 +1,127 @@
"""
Provider selection interface
"""
from colorama import Fore, Style
from providers.factory import ProviderFactory
from core.config import MigrationConfig
def select_providers() -> tuple[str, str]:
"""
Interactive provider selection for source and destination
Returns tuple (source_provider, destination_provider)
"""
config = MigrationConfig()
# Get available providers from factory
available_source_providers = ProviderFactory.get_available_source_providers()
available_destination_providers = ProviderFactory.get_available_destination_providers()
print(f"\n{Fore.CYAN}🔧 Provider Configuration{Style.RESET_ALL}")
print(f"{Fore.YELLOW}Available providers by type:{Style.RESET_ALL}")
print(f" 📥 Source: {', '.join(available_source_providers).upper()}")
print(f" 📤 Destination: {', '.join(available_destination_providers).upper()}")
# Check configured providers
source_provider_configs = config.get_available_source_providers()
dest_provider_configs = config.get_available_destination_providers()
print(f"\n{Fore.YELLOW}Source provider configuration status:{Style.RESET_ALL}")
configured_source_providers = []
for provider, is_configured in source_provider_configs.items():
if provider in available_source_providers:
status = f"{Fore.GREEN}✅ Configured" if is_configured else f"{Fore.RED}❌ Not configured"
print(f" {provider.upper()}: {status}{Style.RESET_ALL}")
if is_configured:
configured_source_providers.append(provider)
print(f"\n{Fore.YELLOW}Destination provider configuration status:{Style.RESET_ALL}")
configured_destination_providers = []
for provider, is_configured in dest_provider_configs.items():
if provider in available_destination_providers:
status = f"{Fore.GREEN}✅ Configured" if is_configured else f"{Fore.RED}❌ Not configured"
print(f" {provider.upper()}: {status}{Style.RESET_ALL}")
if is_configured:
configured_destination_providers.append(provider)
# Validate we have at least one configured source and destination
if not configured_source_providers:
print(f"\n{Fore.RED}❌ No source providers configured!{Style.RESET_ALL}")
print(f"{Fore.YELLOW}💡 Please configure at least one source provider in .env:{Style.RESET_ALL}")
for provider in available_source_providers:
if provider == 'gitea':
print(f" GITEA_SOURCE_URL, GITEA_SOURCE_TOKEN, GITEA_SOURCE_USERNAME")
elif provider == 'gitlab':
print(f" GITLAB_SOURCE_URL, GITLAB_SOURCE_TOKEN, GITLAB_SOURCE_USERNAME")
elif provider == 'github':
print(f" GITHUB_TOKEN, GITHUB_USERNAME")
exit(1)
if not configured_destination_providers:
print(f"\n{Fore.RED}❌ No destination providers configured!{Style.RESET_ALL}")
print(f"{Fore.YELLOW}💡 Please configure at least one destination provider in .env:{Style.RESET_ALL}")
for provider in available_destination_providers:
if provider == 'gitea':
print(f" GITEA_DEST_URL, GITEA_DEST_TOKEN, GITEA_DEST_USERNAME")
elif provider == 'github':
print(f" GITHUB_TOKEN, GITHUB_USERNAME")
elif provider == 'gitlab':
print(f" GITLAB_DEST_URL, GITLAB_DEST_TOKEN, GITLAB_DEST_USERNAME")
exit(1)
# Select source provider
print(f"\n{Fore.CYAN}📥 Select SOURCE provider:{Style.RESET_ALL}")
source_provider = _select_provider(configured_source_providers, "source")
# Select destination provider (exclude GitHub → GitHub)
available_destinations = configured_destination_providers.copy()
if source_provider == 'github' and 'github' in available_destinations:
available_destinations.remove('github')
print(f"\n{Fore.YELLOW} GitHub → GitHub migration not supported (same instance){Style.RESET_ALL}")
if not available_destinations:
print(f"\n{Fore.RED}❌ No valid destination providers available!{Style.RESET_ALL}")
if source_provider == 'github':
print(f"{Fore.YELLOW}GitHub can only migrate TO other providers, not to itself.{Style.RESET_ALL}")
exit(1)
# Select destination provider
print(f"\n{Fore.CYAN}📤 Select DESTINATION provider:{Style.RESET_ALL}")
destination_provider = _select_provider(available_destinations, "destination")
print(f"\n{Fore.GREEN}✅ Migration will be: {source_provider.upper()}{destination_provider.upper()}{Style.RESET_ALL}")
return source_provider, destination_provider
def _select_provider(providers: list, provider_type: str) -> str:
"""
Select a provider from the available list
"""
if len(providers) == 1:
print(f"{Fore.GREEN}✅ Only one {provider_type} provider available: {providers[0].upper()}{Style.RESET_ALL}")
return providers[0]
while True:
print(f"\n{Fore.YELLOW}Available {provider_type} providers:{Style.RESET_ALL}")
for i, provider in enumerate(providers, 1):
print(f" {i}. {provider.upper()}")
try:
choice = input(f"\nEnter your choice (1-{len(providers)}): ").strip()
if choice.isdigit():
index = int(choice) - 1
if 0 <= index < len(providers):
selected = providers[index]
print(f"{Fore.GREEN}✅ Selected {provider_type}: {selected.upper()}{Style.RESET_ALL}")
return selected
print(f"{Fore.RED}❌ Invalid choice. Please enter a number between 1 and {len(providers)}.{Style.RESET_ALL}")
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}🛑 Migration cancelled.{Style.RESET_ALL}")
exit(0)
except EOFError:
print(f"\n{Fore.YELLOW}🛑 Migration cancelled.{Style.RESET_ALL}")
exit(0)
Loading…
Cancel
Save