@@ -18,7 +18,6 @@ use serde_json::{
1818 Value ,
1919} ;
2020use tokio:: fs;
21- use tracing:: debug;
2221
2322use crate :: os:: Os ;
2423use crate :: util:: paths:: GlobalPaths ;
@@ -39,35 +38,20 @@ pub struct MigrateArgs {
3938}
4039
4140impl MigrateArgs {
42- pub async fn execute ( self , _os : & mut Os ) -> Result < ExitCode > {
41+ pub async fn execute ( self , os : & mut Os ) -> Result < ExitCode > {
4342 // Try to acquire migration lock
4443 let _lock = match acquire_migration_lock ( ) ? {
4544 Some ( lock) => lock,
4645 None => {
47- // Another process is migrating, skip silently
46+ println ! ( "Migration already in progress by another process" ) ;
4847 return Ok ( ExitCode :: SUCCESS ) ;
4948 } ,
5049 } ;
5150
52- if !self . yes && !self . dry_run {
53- println ! ( "This will migrate your database and settings from amazon-q to kiro-cli." ) ;
54- println ! ( "\n Migration details:" ) ;
55- println ! ( " • Database: {{data_local_dir}}/amazon-q → {{data_local_dir}}/kiro-cli" ) ;
56- println ! ( " • Settings: {{data_local_dir}}/amazon-q → ~/.aws/kiro-cli" ) ;
57- println ! ( "\n Continue? (y/N)" ) ;
58-
59- let mut input = String :: new ( ) ;
60- std:: io:: stdin ( ) . read_line ( & mut input) ?;
61- if !input. trim ( ) . eq_ignore_ascii_case ( "y" ) {
62- println ! ( "Migration cancelled" ) ;
63- return Ok ( ExitCode :: SUCCESS ) ;
64- }
65- }
66-
67- let status = detect_migration ( ) ?;
51+ let status = detect_migration ( os) . await ?;
6852
6953 if !self . force && matches ! ( status, MigrationStatus :: Completed ) {
70- debug ! ( "✓ Migration already completed" ) ;
54+ println ! ( "✓ Migration already completed" ) ;
7155 return Ok ( ExitCode :: SUCCESS ) ;
7256 }
7357
@@ -78,26 +62,47 @@ impl MigrateArgs {
7862 new_settings,
7963 } = status
8064 else {
81- debug ! ( "✓ No migration needed (fresh install)" ) ;
65+ println ! ( "✓ No migration needed (fresh install)" ) ;
8266 return Ok ( ExitCode :: SUCCESS ) ;
8367 } ;
8468
69+ if !self . yes && !self . dry_run {
70+ println ! ( "This will migrate your database and settings from amazon-q to kiro-cli." ) ;
71+ println ! ( "\n Migration details:" ) ;
72+ println ! ( " • Database: {{data_local_dir}}/amazon-q → {{data_local_dir}}/kiro-cli" ) ;
73+ println ! ( " • Settings: {{data_local_dir}}/amazon-q → ~/.aws/kiro-cli" ) ;
74+ println ! ( "\n Continue? (y/N)" ) ;
75+
76+ let mut input = String :: new ( ) ;
77+ std:: io:: stdin ( ) . read_line ( & mut input) ?;
78+ if !input. trim ( ) . eq_ignore_ascii_case ( "y" ) {
79+ println ! ( "Migration cancelled" ) ;
80+ return Ok ( ExitCode :: SUCCESS ) ;
81+ }
82+ }
83+
8584 // Migrate database
8685 let db_result = migrate_database ( & old_db, & new_db, self . dry_run ) . await ?;
87- debug ! ( "✓ Database: {}" , db_result. message) ;
86+ println ! ( "✓ Database: {}" , db_result. message) ;
87+
88+ // Reload database connection after copying the file
89+ if !self . dry_run && db_result. bytes_copied > 0 {
90+ os. database = crate :: database:: Database :: new ( ) . await ?;
91+ }
8892
8993 // Migrate settings
9094 let settings_result = migrate_settings ( & old_settings, & new_settings, self . dry_run ) . await ?;
91- debug ! ( "✓ Settings: {}" , settings_result. message) ;
95+ println ! ( "✓ Settings: {}" , settings_result. message) ;
9296 if !settings_result. transformations . is_empty ( ) {
93- debug ! ( " Transformations applied:" ) ;
97+ println ! ( " Transformations applied:" ) ;
9498 for t in & settings_result. transformations {
95- debug ! ( " - {t}" ) ;
99+ println ! ( " - {t}" ) ;
96100 }
97101 }
98102
99103 if !self . dry_run {
100- debug ! ( "\n ✓ Migration completed successfully!" ) ;
104+ os. database . set_kiro_migration_completed ( ) ?;
105+ println ! ( "\n ✓ Migration completed successfully!" ) ;
101106 } else {
102107 println ! ( "\n (Dry run - no changes made)" ) ;
103108 }
@@ -119,14 +124,14 @@ enum MigrationStatus {
119124 Completed ,
120125}
121126
122- fn detect_migration ( ) -> Result < MigrationStatus > {
127+ async fn detect_migration ( os : & mut Os ) -> Result < MigrationStatus > {
123128 let old_db = GlobalPaths :: old_database_path ( ) ?;
124129 let old_settings = GlobalPaths :: old_settings_path ( ) ?;
125- let new_db = GlobalPaths :: new_database_path ( ) ?;
126- let new_settings = GlobalPaths :: new_settings_path ( ) ?;
130+ let new_db = GlobalPaths :: database_path ( ) ?;
131+ let new_settings = GlobalPaths :: settings_path ( ) ?;
127132
128133 let old_exists = old_db. exists ( ) || old_settings. exists ( ) ;
129- let migration_completed = GlobalPaths :: is_migration_completed_static ( ) . unwrap_or ( false ) ;
134+ let migration_completed = os . database . is_kiro_migration_completed ( ) . unwrap_or ( false ) ;
130135
131136 if migration_completed {
132137 Ok ( MigrationStatus :: Completed )
@@ -180,7 +185,7 @@ async fn migrate_database(old_path: &Path, new_path: &Path, dry_run: bool) -> Re
180185 if let Some ( parent) = new_path. parent ( ) {
181186 fs:: create_dir_all ( parent)
182187 . await
183- . context ( "Failed to create target directory" ) ?;
188+ . context ( format ! ( "Failed to create target directory: {}" , parent . display ( ) ) ) ?;
184189 }
185190
186191 let bytes = fs:: copy ( old_path, new_path) . await . context ( "Failed to copy database" ) ?;
@@ -248,7 +253,7 @@ async fn migrate_settings(old_path: &Path, new_path: &Path, dry_run: bool) -> Re
248253 if let Some ( parent) = new_path. parent ( ) {
249254 fs:: create_dir_all ( parent)
250255 . await
251- . context ( "Failed to create target directory" ) ?;
256+ . context ( format ! ( "Failed to create target directory: {}" , parent . display ( ) ) ) ?;
252257 }
253258
254259 let json = serde_json:: to_string_pretty ( & transformed) . context ( "Failed to serialize settings" ) ?;
@@ -257,14 +262,25 @@ async fn migrate_settings(old_path: &Path, new_path: &Path, dry_run: bool) -> Re
257262 . context ( "Failed to write settings file" ) ?;
258263
259264 Ok ( SettingsMigrationResult {
260- message : format ! ( "Settings migrated successfully ({} settings)" , transformed . len ( ) ) ,
265+ message : "Settings migrated successfully" . to_string ( ) ,
261266 settings_count : transformed. len ( ) ,
262267 transformations,
263268 } )
264269}
265270
266271// File locking
267- fn acquire_migration_lock ( ) -> Result < Option < std:: fs:: File > > {
272+ struct MigrationLock {
273+ _file : std:: fs:: File ,
274+ path : PathBuf ,
275+ }
276+
277+ impl Drop for MigrationLock {
278+ fn drop ( & mut self ) {
279+ let _ = std:: fs:: remove_file ( & self . path ) ;
280+ }
281+ }
282+
283+ fn acquire_migration_lock ( ) -> Result < Option < MigrationLock > > {
268284 let lock_path = GlobalPaths :: migration_lock_path ( ) ?;
269285
270286 if let Some ( parent) = lock_path. parent ( ) {
@@ -275,10 +291,38 @@ fn acquire_migration_lock() -> Result<Option<std::fs::File>> {
275291 . create ( true )
276292 . write ( true )
277293 . truncate ( false )
278- . open ( lock_path) ?;
294+ . open ( & lock_path) ?;
279295
280296 match flock ( & file, FlockOperation :: NonBlockingLockExclusive ) {
281- Ok ( ( ) ) => Ok ( Some ( file) ) ,
282- Err ( _) => Ok ( None ) ,
297+ Ok ( ( ) ) => Ok ( Some ( MigrationLock {
298+ _file : file,
299+ path : lock_path,
300+ } ) ) ,
301+ Err ( _) => {
302+ // Check if lock is stale (older than 1 minute)
303+ if let Ok ( metadata) = std:: fs:: metadata ( & lock_path) {
304+ if let Ok ( modified) = metadata. modified ( ) {
305+ if let Ok ( elapsed) = modified. elapsed ( ) {
306+ if elapsed. as_secs ( ) > 60 {
307+ // Stale lock - remove and retry
308+ std:: fs:: remove_file ( & lock_path) ?;
309+ let file = std:: fs:: OpenOptions :: new ( )
310+ . create ( true )
311+ . write ( true )
312+ . truncate ( false )
313+ . open ( & lock_path) ?;
314+ return match flock ( & file, FlockOperation :: NonBlockingLockExclusive ) {
315+ Ok ( ( ) ) => Ok ( Some ( MigrationLock {
316+ _file : file,
317+ path : lock_path,
318+ } ) ) ,
319+ Err ( _) => Ok ( None ) ,
320+ } ;
321+ }
322+ }
323+ }
324+ }
325+ Ok ( None )
326+ } ,
283327 }
284328}
0 commit comments