Skip to content

Commit 764b056

Browse files
committed
Address comments
1 parent 7d07791 commit 764b056

File tree

3 files changed

+102
-92
lines changed

3 files changed

+102
-92
lines changed

crates/chat-cli/src/cli/migrate.rs

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use serde_json::{
1818
Value,
1919
};
2020
use tokio::fs;
21-
use tracing::debug;
2221

2322
use crate::os::Os;
2423
use crate::util::paths::GlobalPaths;
@@ -39,35 +38,20 @@ pub struct MigrateArgs {
3938
}
4039

4140
impl 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!("\nMigration 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!("\nContinue? (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!("\nMigration 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!("\nContinue? (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
}

crates/chat-cli/src/database/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const IDC_REGION_KEY: &str = "auth.idc.region";
6363
const CUSTOMIZATION_STATE_KEY: &str = "api.selectedCustomization";
6464
const PROFILE_MIGRATION_KEY: &str = "profile.Migrated";
6565
const HEARTBEAT_DATE_KEY: &str = "telemetry.lastHeartbeatDate";
66+
const KIRO_MIGRATION_KEY: &str = "migration.kiro.completed";
6667

6768
const MIGRATIONS: &[Migration] = migrations![
6869
"000_migration_table",
@@ -355,6 +356,19 @@ impl Database {
355356
Ok(())
356357
}
357358

359+
/// Check if kiro migration has been completed
360+
pub fn is_kiro_migration_completed(&self) -> Result<bool, DatabaseError> {
361+
Ok(self
362+
.get_entry::<bool>(Table::State, KIRO_MIGRATION_KEY)?
363+
.unwrap_or(false))
364+
}
365+
366+
/// Mark kiro migration as completed
367+
pub fn set_kiro_migration_completed(&self) -> Result<(), DatabaseError> {
368+
self.set_entry(Table::State, KIRO_MIGRATION_KEY, true)?;
369+
Ok(())
370+
}
371+
358372
// /// Get the model id used for last conversation state.
359373
// pub fn get_last_used_model_id(&self) -> Result<Option<String>, DatabaseError> {
360374
// self.get_json_entry::<String>(Table::State, LAST_USED_MODEL_ID)

crates/chat-cli/src/util/paths.rs

Lines changed: 7 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -313,17 +313,9 @@ impl<'a> GlobalPaths<'a> {
313313

314314
/// Get settings path (new location with fallback to old)
315315
pub fn settings_path() -> Result<PathBuf> {
316-
let new_path = Self::new_settings_path()?;
317-
if new_path.exists() {
318-
return Ok(new_path);
319-
}
320-
321-
let old_path = Self::old_settings_path()?;
322-
if old_path.exists() {
323-
return Ok(old_path);
324-
}
325-
326-
Ok(new_path)
316+
Ok(dirs::home_dir()
317+
.ok_or(DirectoryError::NoHomeDirectory)?
318+
.join(global::SETTINGS_FILE))
327319
}
328320

329321
pub fn mcp_auth_dir(&self) -> Result<PathBuf> {
@@ -332,32 +324,17 @@ impl<'a> GlobalPaths<'a> {
332324

333325
/// Get database path (new location with fallback to old)
334326
pub fn database_path() -> Result<PathBuf> {
335-
let new_path = Self::new_database_path()?;
336-
if new_path.exists() {
337-
return Ok(new_path);
338-
}
339-
340-
let old_path = Self::old_database_path()?;
341-
if old_path.exists() {
342-
return Ok(old_path);
343-
}
344-
345-
Ok(new_path)
346-
}
347-
348-
/// Get old database path (amazon-q location)
349-
pub fn old_database_path() -> Result<PathBuf> {
350327
Ok(dirs::data_local_dir()
351328
.ok_or(DirectoryError::NoHomeDirectory)?
352-
.join("amazon-q")
329+
.join("kiro-cli")
353330
.join("data.sqlite3"))
354331
}
355332

356-
/// Get new database path (kiro-cli location)
357-
pub fn new_database_path() -> Result<PathBuf> {
333+
/// Get old database path (amazon-q location)
334+
pub fn old_database_path() -> Result<PathBuf> {
358335
Ok(dirs::data_local_dir()
359336
.ok_or(DirectoryError::NoHomeDirectory)?
360-
.join("kiro-cli")
337+
.join("amazon-q")
361338
.join("data.sqlite3"))
362339
}
363340

@@ -369,31 +346,6 @@ impl<'a> GlobalPaths<'a> {
369346
.join("settings.json"))
370347
}
371348

372-
/// Get new settings path (kiro-cli location in home)
373-
pub fn new_settings_path() -> Result<PathBuf> {
374-
Ok(dirs::home_dir()
375-
.ok_or(DirectoryError::NoHomeDirectory)?
376-
.join(global::SETTINGS_FILE))
377-
}
378-
379-
/// Check if migration completed by reading settings file
380-
pub fn is_migration_completed_static() -> Result<bool> {
381-
let new_settings = Self::new_settings_path()?;
382-
if !new_settings.exists() {
383-
return Ok(false);
384-
}
385-
386-
let content = std::fs::read_to_string(&new_settings)?;
387-
let Ok(settings) = serde_json::from_str::<serde_json::Value>(&content) else {
388-
return Ok(false);
389-
};
390-
391-
Ok(settings
392-
.get("migration.kiro.completed")
393-
.and_then(|c| c.as_bool())
394-
.unwrap_or(false))
395-
}
396-
397349
/// Get migration lock file path
398350
pub fn migration_lock_path() -> Result<PathBuf> {
399351
Ok(dirs::home_dir()

0 commit comments

Comments
 (0)