Skip to main content

guts_migrate/
types.rs

1//! Common types for migration operations.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Configuration for a migration operation.
8#[derive(Debug, Clone)]
9pub struct MigrationConfig {
10    /// Source repository identifier (e.g., "owner/repo").
11    pub source_repo: String,
12
13    /// Guts node API URL.
14    pub guts_url: String,
15
16    /// Guts API token for authentication.
17    pub guts_token: Option<String>,
18
19    /// Target repository name on Guts (defaults to source name).
20    pub target_name: Option<String>,
21
22    /// Target owner on Guts (defaults to authenticated user).
23    pub target_owner: Option<String>,
24}
25
26impl MigrationConfig {
27    /// Create a new migration configuration.
28    pub fn new(source_repo: impl Into<String>, guts_url: impl Into<String>) -> Self {
29        Self {
30            source_repo: source_repo.into(),
31            guts_url: guts_url.into(),
32            guts_token: None,
33            target_name: None,
34            target_owner: None,
35        }
36    }
37
38    /// Set the Guts API token.
39    pub fn with_token(mut self, token: impl Into<String>) -> Self {
40        self.guts_token = Some(token.into());
41        self
42    }
43
44    /// Set the target repository name.
45    pub fn with_target_name(mut self, name: impl Into<String>) -> Self {
46        self.target_name = Some(name.into());
47        self
48    }
49
50    /// Set the target owner.
51    pub fn with_target_owner(mut self, owner: impl Into<String>) -> Self {
52        self.target_owner = Some(owner.into());
53        self
54    }
55}
56
57/// Options for controlling what gets migrated.
58#[derive(Debug, Clone)]
59pub struct MigrationOptions {
60    /// Migrate issues.
61    pub migrate_issues: bool,
62
63    /// Migrate pull requests / merge requests.
64    pub migrate_pull_requests: bool,
65
66    /// Migrate releases and assets.
67    pub migrate_releases: bool,
68
69    /// Migrate wiki.
70    pub migrate_wiki: bool,
71
72    /// Migrate labels.
73    pub migrate_labels: bool,
74
75    /// Migrate milestones.
76    pub migrate_milestones: bool,
77
78    /// Include closed issues/PRs.
79    pub include_closed: bool,
80
81    /// Rewrite content links to point to Guts.
82    pub rewrite_links: bool,
83
84    /// Map of source usernames to Guts usernames.
85    pub user_mapping: HashMap<String, String>,
86}
87
88impl Default for MigrationOptions {
89    fn default() -> Self {
90        Self {
91            migrate_issues: true,
92            migrate_pull_requests: true,
93            migrate_releases: true,
94            migrate_wiki: true,
95            migrate_labels: true,
96            migrate_milestones: true,
97            include_closed: true,
98            rewrite_links: true,
99            user_mapping: HashMap::new(),
100        }
101    }
102}
103
104impl MigrationOptions {
105    /// Enable or disable issue migration.
106    pub fn with_issues(mut self, migrate: bool) -> Self {
107        self.migrate_issues = migrate;
108        self
109    }
110
111    /// Enable or disable pull request migration.
112    pub fn with_pull_requests(mut self, migrate: bool) -> Self {
113        self.migrate_pull_requests = migrate;
114        self
115    }
116
117    /// Enable or disable release migration.
118    pub fn with_releases(mut self, migrate: bool) -> Self {
119        self.migrate_releases = migrate;
120        self
121    }
122
123    /// Enable or disable wiki migration.
124    pub fn with_wiki(mut self, migrate: bool) -> Self {
125        self.migrate_wiki = migrate;
126        self
127    }
128
129    /// Enable or disable label migration.
130    pub fn with_labels(mut self, migrate: bool) -> Self {
131        self.migrate_labels = migrate;
132        self
133    }
134
135    /// Enable or disable milestone migration.
136    pub fn with_milestones(mut self, migrate: bool) -> Self {
137        self.migrate_milestones = migrate;
138        self
139    }
140
141    /// Enable or disable including closed items.
142    pub fn with_closed(mut self, include: bool) -> Self {
143        self.include_closed = include;
144        self
145    }
146
147    /// Enable or disable link rewriting.
148    pub fn with_link_rewriting(mut self, rewrite: bool) -> Self {
149        self.rewrite_links = rewrite;
150        self
151    }
152
153    /// Add a user mapping.
154    pub fn with_user_mapping(
155        mut self,
156        source_user: impl Into<String>,
157        guts_user: impl Into<String>,
158    ) -> Self {
159        self.user_mapping
160            .insert(source_user.into(), guts_user.into());
161        self
162    }
163}
164
165/// Report of a completed migration.
166#[derive(Debug, Clone, Default, Serialize, Deserialize)]
167pub struct MigrationReport {
168    /// Whether the repository was created on Guts.
169    pub repo_created: bool,
170
171    /// Whether git data was mirrored successfully.
172    pub git_mirrored: bool,
173
174    /// Number of branches migrated.
175    pub branches_migrated: usize,
176
177    /// Number of tags migrated.
178    pub tags_migrated: usize,
179
180    /// Number of issues migrated.
181    pub issues_migrated: usize,
182
183    /// Number of pull requests migrated.
184    pub prs_migrated: usize,
185
186    /// Number of releases migrated.
187    pub releases_migrated: usize,
188
189    /// Number of release assets migrated.
190    pub assets_migrated: usize,
191
192    /// Whether wiki was migrated.
193    pub wiki_migrated: bool,
194
195    /// Number of labels migrated.
196    pub labels_migrated: usize,
197
198    /// Number of milestones migrated.
199    pub milestones_migrated: usize,
200
201    /// Errors encountered during migration.
202    pub errors: Vec<MigrationErrorInfo>,
203
204    /// Warnings generated during migration.
205    pub warnings: Vec<String>,
206
207    /// Start time of migration.
208    pub started_at: Option<DateTime<Utc>>,
209
210    /// End time of migration.
211    pub completed_at: Option<DateTime<Utc>>,
212
213    /// URL of the migrated repository on Guts.
214    pub guts_repo_url: Option<String>,
215}
216
217impl MigrationReport {
218    /// Create a new empty report.
219    pub fn new() -> Self {
220        Self {
221            started_at: Some(Utc::now()),
222            ..Default::default()
223        }
224    }
225
226    /// Mark the migration as complete.
227    pub fn complete(&mut self) {
228        self.completed_at = Some(Utc::now());
229    }
230
231    /// Check if the migration was successful (no critical errors).
232    pub fn is_successful(&self) -> bool {
233        self.repo_created && self.git_mirrored && self.errors.iter().all(|e| !e.is_critical)
234    }
235
236    /// Get the total number of items migrated.
237    pub fn total_items_migrated(&self) -> usize {
238        self.issues_migrated
239            + self.prs_migrated
240            + self.releases_migrated
241            + self.labels_migrated
242            + self.milestones_migrated
243    }
244
245    /// Add an error to the report.
246    pub fn add_error(&mut self, category: &str, message: &str, is_critical: bool) {
247        self.errors.push(MigrationErrorInfo {
248            category: category.to_string(),
249            message: message.to_string(),
250            is_critical,
251        });
252    }
253
254    /// Add a warning to the report.
255    pub fn add_warning(&mut self, message: impl Into<String>) {
256        self.warnings.push(message.into());
257    }
258
259    /// Get the duration of the migration.
260    pub fn duration(&self) -> Option<chrono::Duration> {
261        match (self.started_at, self.completed_at) {
262            (Some(start), Some(end)) => Some(end - start),
263            _ => None,
264        }
265    }
266
267    /// Print a summary of the migration.
268    pub fn print_summary(&self) {
269        println!("\n=== Migration Summary ===\n");
270        println!(
271            "Repository created: {}",
272            if self.repo_created { "✓" } else { "✗" }
273        );
274        println!(
275            "Git data mirrored:  {}",
276            if self.git_mirrored { "✓" } else { "✗" }
277        );
278
279        if self.branches_migrated > 0 || self.tags_migrated > 0 {
280            println!("Branches migrated:  {}", self.branches_migrated);
281            println!("Tags migrated:      {}", self.tags_migrated);
282        }
283
284        println!("Issues migrated:    {}", self.issues_migrated);
285        println!("PRs migrated:       {}", self.prs_migrated);
286        println!("Releases migrated:  {}", self.releases_migrated);
287        println!("Assets migrated:    {}", self.assets_migrated);
288        println!(
289            "Wiki migrated:      {}",
290            if self.wiki_migrated { "✓" } else { "N/A" }
291        );
292        println!("Labels migrated:    {}", self.labels_migrated);
293        println!("Milestones:         {}", self.milestones_migrated);
294
295        if let Some(url) = &self.guts_repo_url {
296            println!("\nRepository URL: {url}");
297        }
298
299        if let Some(duration) = self.duration() {
300            println!("\nCompleted in {} seconds", duration.num_seconds());
301        }
302
303        if !self.errors.is_empty() {
304            println!("\nErrors ({}):", self.errors.len());
305            for error in &self.errors {
306                let severity = if error.is_critical {
307                    "CRITICAL"
308                } else {
309                    "WARNING"
310                };
311                println!("  [{severity}] {}: {}", error.category, error.message);
312            }
313        }
314
315        if !self.warnings.is_empty() {
316            println!("\nWarnings ({}):", self.warnings.len());
317            for warning in &self.warnings {
318                println!("  - {warning}");
319            }
320        }
321
322        let status = if self.is_successful() {
323            "SUCCESS"
324        } else {
325            "FAILED"
326        };
327        println!("\nOverall Status: {status}");
328    }
329}
330
331/// Information about an error that occurred during migration.
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct MigrationErrorInfo {
334    /// Category of the error (e.g., "issues", "git", "api").
335    pub category: String,
336
337    /// Error message.
338    pub message: String,
339
340    /// Whether this error is critical (blocks migration success).
341    pub is_critical: bool,
342}
343
344/// Source platform types.
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
346#[serde(rename_all = "lowercase")]
347pub enum SourcePlatform {
348    /// GitHub.
349    GitHub,
350    /// GitLab.
351    GitLab,
352    /// Bitbucket.
353    Bitbucket,
354}
355
356impl std::fmt::Display for SourcePlatform {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        match self {
359            Self::GitHub => write!(f, "GitHub"),
360            Self::GitLab => write!(f, "GitLab"),
361            Self::Bitbucket => write!(f, "Bitbucket"),
362        }
363    }
364}
365
366/// Issue state.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "lowercase")]
369pub enum IssueState {
370    Open,
371    Closed,
372}
373
374/// Pull request state.
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
376#[serde(rename_all = "lowercase")]
377pub enum PullRequestState {
378    Open,
379    Closed,
380    Merged,
381}
382
383/// Migrated issue representation.
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct MigratedIssue {
386    pub source_number: u64,
387    pub guts_number: u64,
388    pub title: String,
389    pub state: IssueState,
390}
391
392/// Migrated pull request representation.
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct MigratedPullRequest {
395    pub source_number: u64,
396    pub guts_number: u64,
397    pub title: String,
398    pub state: PullRequestState,
399}
400
401/// Migrated release representation.
402#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct MigratedRelease {
404    pub tag_name: String,
405    pub name: String,
406    pub assets_count: usize,
407}