commit
008cbfd586
@ -0,0 +1,106 @@
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
.env.development
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
migration.log
|
||||
|
||||
# Temporary directories for migration
|
||||
temp_*
|
||||
migration_*/
|
@ -0,0 +1,192 @@
|
||||
# 🚀 Gitea to GitHub Migration Tool
|
||||
|
||||
Ce projet fournit un outil pratique et modulable pour migrer vos repositories de Gitea vers GitHub automatiquement.
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
- **Migration automatique** : Migre tous vos repositories Gitea vers GitHub en une seule commande
|
||||
- **Migration sélective** : Choisissez spécifiquement quels repositories migrer
|
||||
- **Support multi-propriétaire** : Migrez des repositories d'autres utilisateurs/organisations auxquels vous avez accès
|
||||
- **Interface en ligne de commande** : Interface colorée et intuitive
|
||||
- **Logging complet** : Suivi détaillé des opérations avec fichier de log
|
||||
- **Gestion des erreurs** : Robuste avec gestion gracieuse des erreurs
|
||||
|
||||
## 🛠 Installation
|
||||
|
||||
1. **Clonez le repository** :
|
||||
```bash
|
||||
git clone https://github.com/votre-username/GiteaToGithubMigrator.git
|
||||
cd GiteaToGithubMigrator
|
||||
```
|
||||
|
||||
2. **Configuration automatique** :
|
||||
```bash
|
||||
./run.sh --setup
|
||||
```
|
||||
|
||||
Le script va automatiquement :
|
||||
- Créer un environnement virtuel Python
|
||||
- Installer toutes les dépendances
|
||||
- Créer le fichier de configuration `.env`
|
||||
|
||||
Cela créera un fichier `.env` que vous devrez remplir avec vos informations :
|
||||
|
||||
```env
|
||||
# Gitea Configuration
|
||||
GITEA_URL=https://votre-instance-gitea.com
|
||||
GITEA_TOKEN=votre_token_gitea
|
||||
GITEA_USERNAME=votre_nom_utilisateur_gitea
|
||||
|
||||
# GitHub Configuration
|
||||
GITHUB_TOKEN=votre_token_github
|
||||
GITHUB_USERNAME=votre_nom_utilisateur_github
|
||||
```
|
||||
|
||||
## 🔑 Configuration des tokens
|
||||
|
||||
### Token Gitea
|
||||
1. Allez dans **Settings** → **Applications** → **Generate New Token**
|
||||
2. Donnez un nom au token et sélectionnez les permissions :
|
||||
- `repo` (accès complet aux repositories)
|
||||
- `user` (accès aux informations utilisateur)
|
||||
|
||||
### Token GitHub
|
||||
1. Allez dans **Settings** → **Developer settings** → **Personal access tokens** → **Tokens (classic)**
|
||||
2. Cliquez sur **Generate new token (classic)**
|
||||
3. Sélectionnez les permissions :
|
||||
- `repo` (accès complet aux repositories privés)
|
||||
- `public_repo` (accès aux repositories publics)
|
||||
|
||||
## 🚀 Utilisation
|
||||
|
||||
Après avoir configuré vos tokens dans le fichier `.env`, utilisez le script de lancement :
|
||||
|
||||
### Migration de tous vos repositories
|
||||
```bash
|
||||
./run.sh
|
||||
```
|
||||
|
||||
### Migration de repositories spécifiques
|
||||
```bash
|
||||
./run.sh --repos mon-repo autre-repo
|
||||
```
|
||||
|
||||
### Migration de repositories d'autres propriétaires
|
||||
```bash
|
||||
./run.sh --repos proprietaire/repo-name
|
||||
```
|
||||
|
||||
### Lister les repositories disponibles
|
||||
```bash
|
||||
./run.sh --list
|
||||
```
|
||||
|
||||
### Mode verbose (plus de détails)
|
||||
```bash
|
||||
./run.sh --verbose
|
||||
```
|
||||
|
||||
> **💡 Alternative** : Vous pouvez aussi utiliser directement `python migrate.py` si vous avez activé l'environnement virtuel (`source venv/bin/activate`)
|
||||
|
||||
## 📋 Exemples d'utilisation
|
||||
|
||||
### Exemple 1 : Migration complète
|
||||
```bash
|
||||
# Migre tous vos repositories
|
||||
./run.sh
|
||||
```
|
||||
|
||||
### Exemple 2 : Migration sélective
|
||||
```bash
|
||||
# Migre seulement les repositories spécifiés
|
||||
./run.sh --repos projet-web api-backend
|
||||
```
|
||||
|
||||
### Exemple 3 : Migration depuis une organisation
|
||||
```bash
|
||||
# Migre un repository d'une organisation
|
||||
./run.sh --repos mon-org/projet-important
|
||||
```
|
||||
|
||||
### Exemple 4 : Premier lancement (configuration)
|
||||
```bash
|
||||
# 1. Setup initial
|
||||
./run.sh --setup
|
||||
|
||||
# 2. Éditez le fichier .env avec vos credentials
|
||||
nano .env
|
||||
|
||||
# 3. Listez vos repositories disponibles
|
||||
./run.sh --list
|
||||
|
||||
# 4. Lancez la migration
|
||||
./run.sh
|
||||
```
|
||||
|
||||
## 📊 Résultats
|
||||
|
||||
L'outil affiche un résumé détaillé à la fin :
|
||||
- ✅ Nombre de migrations réussies
|
||||
- ❌ Nombre de migrations échouées
|
||||
- 📝 Détail par repository
|
||||
|
||||
Tous les logs sont également sauvegardés dans `migration.log`.
|
||||
|
||||
## 🔧 Structure du projet
|
||||
|
||||
```
|
||||
GiteaToGithubMigrator/
|
||||
├── migrate.py # Script principal
|
||||
├── config.py # Gestion de la configuration
|
||||
├── gitea_client.py # Client API Gitea
|
||||
├── github_client.py # Client API GitHub
|
||||
├── migration_tool.py # Logique de migration
|
||||
├── requirements.txt # Dépendances Python
|
||||
├── .env # Configuration (à créer)
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
## ⚠️ Prérequis
|
||||
|
||||
- Python 3.7+
|
||||
- Git installé sur votre système
|
||||
- Accès aux APIs Gitea et GitHub
|
||||
- Tokens d'authentification valides
|
||||
|
||||
## 🛡 Sécurité
|
||||
|
||||
- Les tokens sont stockés dans un fichier `.env` (ajoutez-le à `.gitignore`)
|
||||
- Les URLs d'authentification ne sont jamais loggées
|
||||
- Nettoyage automatique des repositories temporaires
|
||||
|
||||
## 🐛 Résolution de problèmes
|
||||
|
||||
### Erreur d'authentification
|
||||
- Vérifiez que vos tokens sont valides et ont les bonnes permissions
|
||||
- Assurez-vous que les noms d'utilisateur correspondent
|
||||
|
||||
### Erreur de clonage
|
||||
- Vérifiez votre connexion internet
|
||||
- Assurez-vous que Git est installé et accessible
|
||||
|
||||
### Repository déjà existant
|
||||
- L'outil vérifie automatiquement l'existence sur GitHub
|
||||
- Les repositories existants sont ignorés avec un avertissement
|
||||
|
||||
## 📝 Logs
|
||||
|
||||
Tous les détails d'exécution sont sauvegardés dans `migration.log` :
|
||||
- Timestamps des opérations
|
||||
- Détails des erreurs
|
||||
- Statistiques de migration
|
||||
|
||||
## 🤝 Contribution
|
||||
|
||||
Les contributions sont les bienvenues ! N'hésitez pas à :
|
||||
- Signaler des bugs
|
||||
- Proposer des améliorations
|
||||
- Soumettre des pull requests
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Ce projet est sous licence MIT. Voir le fichier LICENSE pour plus de détails.
|
@ -0,0 +1,45 @@
|
||||
"""
|
||||
Configuration module for Gitea to GitHub migration tool
|
||||
"""
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
class Config:
|
||||
"""Configuration class for migration settings"""
|
||||
|
||||
def __init__(self):
|
||||
self.gitea_url = os.getenv('GITEA_URL', 'https://codefirst.iut.uca.fr/git')
|
||||
self.gitea_token = os.getenv('GITEA_TOKEN')
|
||||
self.github_token = os.getenv('GITHUB_TOKEN')
|
||||
self.github_username = os.getenv('GITHUB_USERNAME')
|
||||
self.gitea_username = os.getenv('GITEA_USERNAME')
|
||||
|
||||
# Validate required configuration
|
||||
self._validate_config()
|
||||
|
||||
def _validate_config(self):
|
||||
"""Validate that all required configuration is present"""
|
||||
missing = []
|
||||
|
||||
if not self.gitea_token:
|
||||
missing.append('GITEA_TOKEN')
|
||||
if not self.github_token:
|
||||
missing.append('GITHUB_TOKEN')
|
||||
if not self.github_username:
|
||||
missing.append('GITHUB_USERNAME')
|
||||
if not self.gitea_username:
|
||||
missing.append('GITEA_USERNAME')
|
||||
|
||||
if missing:
|
||||
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
||||
|
||||
def is_valid(self):
|
||||
"""Check if configuration is valid"""
|
||||
try:
|
||||
self._validate_config()
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
@ -0,0 +1,87 @@
|
||||
"""
|
||||
Gitea API client for repository operations
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
class GiteaClient:
|
||||
"""Client for interacting with Gitea API"""
|
||||
|
||||
def __init__(self, base_url: str, token: str, username: str):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.token = token
|
||||
self.username = username
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'Authorization': f'token {token}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
|
||||
def get_user_repos(self) -> List[Dict]:
|
||||
"""Get all repositories owned by the user"""
|
||||
url = f"{self.base_url}/api/v1/user/repos"
|
||||
params = {
|
||||
'limit': 100, # Adjust as needed
|
||||
'page': 1
|
||||
}
|
||||
|
||||
all_repos = []
|
||||
|
||||
while True:
|
||||
response = self.session.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
repos = response.json()
|
||||
if not repos:
|
||||
break
|
||||
|
||||
all_repos.extend(repos)
|
||||
params['page'] += 1
|
||||
|
||||
# Break if we got less than the limit (last page)
|
||||
if len(repos) < params.get('limit', 100):
|
||||
break
|
||||
|
||||
return all_repos
|
||||
|
||||
def get_repo_info(self, owner: str, repo_name: str) -> Optional[Dict]:
|
||||
"""Get information about a specific repository"""
|
||||
url = f"{self.base_url}/api/v1/repos/{owner}/{repo_name}"
|
||||
|
||||
try:
|
||||
response = self.session.get(url)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException:
|
||||
return None
|
||||
|
||||
def get_repo_clone_url(self, owner: str, repo_name: str) -> str:
|
||||
"""Get the clone URL for a repository with authentication"""
|
||||
return f"{self.base_url}/{owner}/{repo_name}.git"
|
||||
|
||||
def list_accessible_repos(self) -> List[Dict]:
|
||||
"""List all repositories the user has access to (including organizations)"""
|
||||
url = f"{self.base_url}/api/v1/user/repos"
|
||||
params = {
|
||||
'limit': 100,
|
||||
'page': 1
|
||||
}
|
||||
|
||||
all_repos = []
|
||||
|
||||
while True:
|
||||
response = self.session.get(url, params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
repos = response.json()
|
||||
if not repos:
|
||||
break
|
||||
|
||||
all_repos.extend(repos)
|
||||
params['page'] += 1
|
||||
|
||||
if len(repos) < params.get('limit', 100):
|
||||
break
|
||||
|
||||
return all_repos
|
@ -0,0 +1,54 @@
|
||||
"""
|
||||
GitHub API client for repository operations
|
||||
"""
|
||||
from github import Github
|
||||
from github.GithubException import GithubException
|
||||
from typing import Optional, Dict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GitHubClient:
|
||||
"""Client for interacting with GitHub API"""
|
||||
|
||||
def __init__(self, token: str, username: str):
|
||||
self.github = Github(token)
|
||||
self.username = username
|
||||
self.user = self.github.get_user()
|
||||
|
||||
def create_repository(self, repo_name: str, description: str = "", private: bool = False) -> bool:
|
||||
"""Create a new repository on GitHub"""
|
||||
try:
|
||||
# Check if repository already exists
|
||||
if self.repository_exists(repo_name):
|
||||
logger.warning(f"Repository {repo_name} already exists on GitHub")
|
||||
return True
|
||||
|
||||
self.user.create_repo(
|
||||
name=repo_name,
|
||||
description=description,
|
||||
private=private,
|
||||
auto_init=False # Don't auto-init since we'll push existing content
|
||||
)
|
||||
logger.info(f"Created repository: {repo_name}")
|
||||
return True
|
||||
|
||||
except GithubException as e:
|
||||
logger.error(f"Failed to create repository {repo_name}: {e}")
|
||||
return False
|
||||
|
||||
def repository_exists(self, repo_name: str) -> bool:
|
||||
"""Check if a repository exists"""
|
||||
try:
|
||||
self.user.get_repo(repo_name)
|
||||
return True
|
||||
except GithubException:
|
||||
return False
|
||||
|
||||
def get_repo_clone_url(self, repo_name: str) -> str:
|
||||
"""Get the clone URL for a GitHub repository"""
|
||||
return f"https://github.com/{self.username}/{repo_name}.git"
|
||||
|
||||
def get_authenticated_clone_url(self, repo_name: str, token: str) -> str:
|
||||
"""Get the authenticated clone URL for pushing"""
|
||||
return f"https://{token}@github.com/{self.username}/{repo_name}.git"
|
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Gitea to GitHub Migration Tool
|
||||
|
||||
This script migrates repositories from Gitea to GitHub.
|
||||
It can migrate all user repositories or specific ones.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from colorama import init, Fore, Style
|
||||
from pathlib import Path
|
||||
|
||||
from config import Config
|
||||
from migration_tool import MigrationTool
|
||||
|
||||
# Initialize colorama for cross-platform colored output
|
||||
init()
|
||||
|
||||
def setup_logging(verbose: bool = False):
|
||||
"""Setup logging configuration"""
|
||||
level = logging.DEBUG if verbose else logging.INFO
|
||||
format_str = '%(asctime)s - %(levelname)s - %(message)s'
|
||||
|
||||
logging.basicConfig(
|
||||
level=level,
|
||||
format=format_str,
|
||||
handlers=[
|
||||
logging.StreamHandler(sys.stdout),
|
||||
logging.FileHandler('migration.log')
|
||||
]
|
||||
)
|
||||
|
||||
def print_banner():
|
||||
"""Print application banner"""
|
||||
banner = f"""
|
||||
{Fore.CYAN}╔═══════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🚀 Gitea to GitHub Migration Tool 🚀 ║
|
||||
║ ║
|
||||
║ Migrates your repositories from Gitea to GitHub seamlessly ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════╝{Style.RESET_ALL}
|
||||
"""
|
||||
print(banner)
|
||||
|
||||
def print_success_summary(results: dict):
|
||||
"""Print migration results summary"""
|
||||
successful = sum(1 for success in results.values() if success)
|
||||
total = len(results)
|
||||
|
||||
print(f"\n{Fore.GREEN}{'='*60}")
|
||||
print(f" MIGRATION SUMMARY")
|
||||
print(f"{'='*60}{Style.RESET_ALL}")
|
||||
print(f"{Fore.GREEN}✅ Successful migrations: {successful}/{total}{Style.RESET_ALL}")
|
||||
|
||||
if successful < total:
|
||||
print(f"{Fore.RED}❌ Failed migrations: {total - successful}/{total}{Style.RESET_ALL}")
|
||||
|
||||
print(f"\n{Fore.CYAN}Detailed results:{Style.RESET_ALL}")
|
||||
for repo, success in results.items():
|
||||
status = f"{Fore.GREEN}✅ SUCCESS" if success else f"{Fore.RED}❌ FAILED"
|
||||
print(f" {repo}: {status}{Style.RESET_ALL}")
|
||||
|
||||
def create_env_template():
|
||||
"""Create a .env template file if it doesn't exist"""
|
||||
env_file = Path('.env')
|
||||
|
||||
if not env_file.exists():
|
||||
template = """# Gitea Configuration
|
||||
GITEA_URL=https://your-gitea-instance.com
|
||||
GITEA_TOKEN=your_gitea_personal_access_token
|
||||
GITEA_USERNAME=your_gitea_username
|
||||
|
||||
# GitHub Configuration
|
||||
GITHUB_TOKEN=your_github_personal_access_token
|
||||
GITHUB_USERNAME=your_github_username
|
||||
"""
|
||||
env_file.write_text(template)
|
||||
print(f"{Fore.YELLOW}📝 Created .env template file. Please fill it with your credentials.{Style.RESET_ALL}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Main application entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Migrate repositories from Gitea to GitHub",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s # Migrate all your repositories
|
||||
%(prog)s --repos repo1 repo2 # Migrate specific repositories
|
||||
%(prog)s --repos owner/repo1 # Migrate repositories from other owners
|
||||
%(prog)s --list # List available repositories
|
||||
%(prog)s --verbose # Enable verbose logging
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--repos',
|
||||
nargs='+',
|
||||
help='Specific repositories to migrate (format: repo_name or owner/repo_name)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--list',
|
||||
action='store_true',
|
||||
help='List all available repositories and exit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose', '-v',
|
||||
action='store_true',
|
||||
help='Enable verbose logging'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--setup',
|
||||
action='store_true',
|
||||
help='Create .env template file'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print_banner()
|
||||
setup_logging(args.verbose)
|
||||
|
||||
# Handle setup command
|
||||
if args.setup:
|
||||
create_env_template()
|
||||
return
|
||||
|
||||
# Check if .env file exists
|
||||
if not create_env_template():
|
||||
return
|
||||
|
||||
try:
|
||||
# Initialize configuration
|
||||
config = Config()
|
||||
|
||||
# Initialize migration tool
|
||||
migration_tool = MigrationTool(config)
|
||||
|
||||
# Handle list command
|
||||
if args.list:
|
||||
print(f"{Fore.CYAN}📋 Available repositories:{Style.RESET_ALL}")
|
||||
repos = migration_tool.list_available_repos()
|
||||
|
||||
for repo in repos:
|
||||
owner = repo['owner']['login']
|
||||
name = repo['name']
|
||||
private = "🔒 Private" if repo.get('private', False) else "🌐 Public"
|
||||
description = repo.get('description', 'No description')
|
||||
|
||||
print(f" {Fore.BLUE}{owner}/{name}{Style.RESET_ALL} - {private}")
|
||||
if description:
|
||||
print(f" 📝 {description}")
|
||||
|
||||
print(f"\n{Fore.GREEN}Total repositories: {len(repos)}{Style.RESET_ALL}")
|
||||
return
|
||||
|
||||
# Perform migration
|
||||
if args.repos:
|
||||
print(f"{Fore.CYAN}🎯 Migrating specific repositories: {', '.join(args.repos)}{Style.RESET_ALL}")
|
||||
results = migration_tool.migrate_specific_repos(args.repos)
|
||||
else:
|
||||
print(f"{Fore.CYAN}🚀 Migrating all your repositories...{Style.RESET_ALL}")
|
||||
results = migration_tool.migrate_all_user_repos()
|
||||
|
||||
# Print results
|
||||
if results:
|
||||
print_success_summary(results)
|
||||
else:
|
||||
print(f"{Fore.YELLOW}⚠️ No repositories found to migrate.{Style.RESET_ALL}")
|
||||
|
||||
except ValueError as e:
|
||||
print(f"{Fore.RED}❌ Configuration error: {e}{Style.RESET_ALL}")
|
||||
print(f"{Fore.YELLOW}💡 Run '{sys.argv[0]} --setup' to create a configuration template.{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Unexpected error: {e}")
|
||||
print(f"{Fore.RED}❌ An unexpected error occurred. Check migration.log for details.{Style.RESET_ALL}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,161 @@
|
||||
"""
|
||||
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()
|
@ -0,0 +1,4 @@
|
||||
requests==2.31.0
|
||||
PyGithub==1.59.1
|
||||
python-dotenv==1.0.0
|
||||
colorama==0.4.6
|
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Gitea to GitHub Migration Tool Launcher
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "🔧 Setting up virtual environment..."
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
else
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
# Run the migration tool with all passed arguments
|
||||
python migrate.py "$@"
|
Loading…
Reference in new issue