use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct MigrationConfig {
pub source_repo: String,
pub guts_url: String,
pub guts_token: Option<String>,
pub target_name: Option<String>,
pub target_owner: Option<String>,
}
impl MigrationConfig {
pub fn new(source_repo: impl Into<String>, guts_url: impl Into<String>) -> Self {
Self {
source_repo: source_repo.into(),
guts_url: guts_url.into(),
guts_token: None,
target_name: None,
target_owner: None,
}
}
pub fn with_token(mut self, token: impl Into<String>) -> Self {
self.guts_token = Some(token.into());
self
}
pub fn with_target_name(mut self, name: impl Into<String>) -> Self {
self.target_name = Some(name.into());
self
}
pub fn with_target_owner(mut self, owner: impl Into<String>) -> Self {
self.target_owner = Some(owner.into());
self
}
}
#[derive(Debug, Clone)]
pub struct MigrationOptions {
pub migrate_issues: bool,
pub migrate_pull_requests: bool,
pub migrate_releases: bool,
pub migrate_wiki: bool,
pub migrate_labels: bool,
pub migrate_milestones: bool,
pub include_closed: bool,
pub rewrite_links: bool,
pub user_mapping: HashMap<String, String>,
}
impl Default for MigrationOptions {
fn default() -> Self {
Self {
migrate_issues: true,
migrate_pull_requests: true,
migrate_releases: true,
migrate_wiki: true,
migrate_labels: true,
migrate_milestones: true,
include_closed: true,
rewrite_links: true,
user_mapping: HashMap::new(),
}
}
}
impl MigrationOptions {
pub fn with_issues(mut self, migrate: bool) -> Self {
self.migrate_issues = migrate;
self
}
pub fn with_pull_requests(mut self, migrate: bool) -> Self {
self.migrate_pull_requests = migrate;
self
}
pub fn with_releases(mut self, migrate: bool) -> Self {
self.migrate_releases = migrate;
self
}
pub fn with_wiki(mut self, migrate: bool) -> Self {
self.migrate_wiki = migrate;
self
}
pub fn with_labels(mut self, migrate: bool) -> Self {
self.migrate_labels = migrate;
self
}
pub fn with_milestones(mut self, migrate: bool) -> Self {
self.migrate_milestones = migrate;
self
}
pub fn with_closed(mut self, include: bool) -> Self {
self.include_closed = include;
self
}
pub fn with_link_rewriting(mut self, rewrite: bool) -> Self {
self.rewrite_links = rewrite;
self
}
pub fn with_user_mapping(
mut self,
source_user: impl Into<String>,
guts_user: impl Into<String>,
) -> Self {
self.user_mapping
.insert(source_user.into(), guts_user.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MigrationReport {
pub repo_created: bool,
pub git_mirrored: bool,
pub branches_migrated: usize,
pub tags_migrated: usize,
pub issues_migrated: usize,
pub prs_migrated: usize,
pub releases_migrated: usize,
pub assets_migrated: usize,
pub wiki_migrated: bool,
pub labels_migrated: usize,
pub milestones_migrated: usize,
pub errors: Vec<MigrationErrorInfo>,
pub warnings: Vec<String>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub guts_repo_url: Option<String>,
}
impl MigrationReport {
pub fn new() -> Self {
Self {
started_at: Some(Utc::now()),
..Default::default()
}
}
pub fn complete(&mut self) {
self.completed_at = Some(Utc::now());
}
pub fn is_successful(&self) -> bool {
self.repo_created && self.git_mirrored && self.errors.iter().all(|e| !e.is_critical)
}
pub fn total_items_migrated(&self) -> usize {
self.issues_migrated
+ self.prs_migrated
+ self.releases_migrated
+ self.labels_migrated
+ self.milestones_migrated
}
pub fn add_error(&mut self, category: &str, message: &str, is_critical: bool) {
self.errors.push(MigrationErrorInfo {
category: category.to_string(),
message: message.to_string(),
is_critical,
});
}
pub fn add_warning(&mut self, message: impl Into<String>) {
self.warnings.push(message.into());
}
pub fn duration(&self) -> Option<chrono::Duration> {
match (self.started_at, self.completed_at) {
(Some(start), Some(end)) => Some(end - start),
_ => None,
}
}
pub fn print_summary(&self) {
println!("\n=== Migration Summary ===\n");
println!(
"Repository created: {}",
if self.repo_created { "✓" } else { "✗" }
);
println!(
"Git data mirrored: {}",
if self.git_mirrored { "✓" } else { "✗" }
);
if self.branches_migrated > 0 || self.tags_migrated > 0 {
println!("Branches migrated: {}", self.branches_migrated);
println!("Tags migrated: {}", self.tags_migrated);
}
println!("Issues migrated: {}", self.issues_migrated);
println!("PRs migrated: {}", self.prs_migrated);
println!("Releases migrated: {}", self.releases_migrated);
println!("Assets migrated: {}", self.assets_migrated);
println!(
"Wiki migrated: {}",
if self.wiki_migrated { "✓" } else { "N/A" }
);
println!("Labels migrated: {}", self.labels_migrated);
println!("Milestones: {}", self.milestones_migrated);
if let Some(url) = &self.guts_repo_url {
println!("\nRepository URL: {url}");
}
if let Some(duration) = self.duration() {
println!("\nCompleted in {} seconds", duration.num_seconds());
}
if !self.errors.is_empty() {
println!("\nErrors ({}):", self.errors.len());
for error in &self.errors {
let severity = if error.is_critical {
"CRITICAL"
} else {
"WARNING"
};
println!(" [{severity}] {}: {}", error.category, error.message);
}
}
if !self.warnings.is_empty() {
println!("\nWarnings ({}):", self.warnings.len());
for warning in &self.warnings {
println!(" - {warning}");
}
}
let status = if self.is_successful() {
"SUCCESS"
} else {
"FAILED"
};
println!("\nOverall Status: {status}");
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrationErrorInfo {
pub category: String,
pub message: String,
pub is_critical: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SourcePlatform {
GitHub,
GitLab,
Bitbucket,
}
impl std::fmt::Display for SourcePlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GitHub => write!(f, "GitHub"),
Self::GitLab => write!(f, "GitLab"),
Self::Bitbucket => write!(f, "Bitbucket"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum IssueState {
Open,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PullRequestState {
Open,
Closed,
Merged,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigratedIssue {
pub source_number: u64,
pub guts_number: u64,
pub title: String,
pub state: IssueState,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigratedPullRequest {
pub source_number: u64,
pub guts_number: u64,
pub title: String,
pub state: PullRequestState,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigratedRelease {
pub tag_name: String,
pub name: String,
pub assets_count: usize,
}