diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-23 01:45:39 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-23 01:59:59 +0900 |
| commit | 7090482473902f53365f96ba4364dd115e53601a (patch) | |
| tree | ab57c931152843c34a7c1f6f474c295c31a2bab0 /crates/mozart/src/commands/self_update.rs | |
| parent | 8199bf2f432968eef4b8f2abd998995b0364a1d5 (diff) | |
| download | php-mozart-7090482473902f53365f96ba4364dd115e53601a.tar.gz php-mozart-7090482473902f53365f96ba4364dd115e53601a.tar.zst php-mozart-7090482473902f53365f96ba4364dd115e53601a.zip | |
fix(self-update): preserve backups and improve output messages
- clean-backups now preserves the most recent backup for rollback
- Rollback no longer deletes the backup file after restoring
- Show version and channel in update/already-up-to-date messages
- Print rollback suggestion after successful update
- Show version instead of file path in rollback output
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/commands/self_update.rs')
| -rw-r--r-- | crates/mozart/src/commands/self_update.rs | 94 |
1 files changed, 80 insertions, 14 deletions
diff --git a/crates/mozart/src/commands/self_update.rs b/crates/mozart/src/commands/self_update.rs index 56e6bf2..0c2f0f6 100644 --- a/crates/mozart/src/commands/self_update.rs +++ b/crates/mozart/src/commands/self_update.rs @@ -127,6 +127,23 @@ fn platform_asset_name() -> anyhow::Result<String> { } } +// ─── Channel helper ─────────────────────────────────────────────────────────── + +fn effective_channel(preview: bool) -> &'static str { + if preview { "preview" } else { "stable" } +} + +// ─── Backup version helper ──────────────────────────────────────────────────── + +fn version_from_backup(path: &Path) -> String { + path.file_name() + .and_then(|n| n.to_str()) + .and_then(|n| n.strip_prefix("mozart-")) + .and_then(|n| n.strip_suffix(BACKUP_EXTENSION)) + .unwrap_or("unknown") + .to_string() +} + // ─── GitHub fetching ────────────────────────────────────────────────────────── async fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease>> { @@ -265,8 +282,7 @@ async fn download_asset( async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { let current_version = get_current_version(); - - println!("Updating Mozart..."); + let channel = effective_channel(args.preview); // Fetch releases let releases = fetch_releases(args.preview).await?; @@ -285,12 +301,25 @@ async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> a println!( "{}", console_format!( - "<info>Mozart is already at the latest version ({current_version})</info>" + "<info>You are already using the latest available Mozart version {current_version} ({channel} channel).</info>" ) ); + + if args.clean_backups { + // Preserve the most recent backup + let latest = find_latest_backup(data_dir).ok(); + clean_backups(data_dir, latest.as_deref())?; + println!( + "{}", + console_format!("<comment>Old backups removed.</comment>") + ); + } + return Ok(()); } + println!("Upgrading to version {target_version} ({channel} channel)."); + // Find the platform asset let asset_name = platform_asset_name()?; let asset = find_asset(target_release, &asset_name)?; @@ -339,9 +368,10 @@ async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> a "<info>Mozart updated successfully from {current_version} to {target_version}</info>" ) ); + println!("Use `mozart self-update --rollback` to return to version {current_version}"); if args.clean_backups { - clean_backups(data_dir)?; + clean_backups(data_dir, Some(&backup_path))?; println!( "{}", console_format!("<comment>Old backups removed.</comment>") @@ -355,8 +385,9 @@ async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> a fn rollback(current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { let backup = find_latest_backup(data_dir)?; + let backup_version = version_from_backup(&backup); - println!("Rolling back to {}...", backup.display()); + println!("Rolling back to version {backup_version}..."); // Set executable permission on Unix before replacing #[cfg(unix)] @@ -371,15 +402,9 @@ fn rollback(current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { self_replace::self_replace(&backup) .map_err(|e| anyhow::anyhow!("Could not restore backup: {e}"))?; - // Remove the backup file we just restored from - let _ = std::fs::remove_file(&backup); - println!( "{}", - console_format!( - "<info>Rollback successful. Restored from {}</info>", - backup.file_name().unwrap_or_default().to_string_lossy() - ) + console_format!("<info>Rollback successful. Restored version {backup_version}</info>") ); let _ = current_exe; // suppress unused warning @@ -420,7 +445,7 @@ fn find_latest_backup(data_dir: &Path) -> anyhow::Result<PathBuf> { Ok(backups.into_iter().next().unwrap()) } -fn clean_backups(data_dir: &Path) -> anyhow::Result<()> { +fn clean_backups(data_dir: &Path, except: Option<&Path>) -> anyhow::Result<()> { let entries = std::fs::read_dir(data_dir).map_err(|e| { anyhow::anyhow!("Could not read data directory {}: {e}", data_dir.display()) })?; @@ -434,6 +459,10 @@ fn clean_backups(data_dir: &Path) -> anyhow::Result<()> { .unwrap_or(false); if is_backup { + if let Some(exc) = except + && path == exc { + continue; + } std::fs::remove_file(&path) .map_err(|e| anyhow::anyhow!("Could not remove backup {}: {e}", path.display()))?; } @@ -674,10 +703,47 @@ mod tests { fs::write(&backup2, b"binary").unwrap(); fs::write(&keep, b"keep me").unwrap(); - clean_backups(dir.path()).expect("clean_backups should succeed"); + clean_backups(dir.path(), None).expect("clean_backups should succeed"); assert!(!backup1.exists(), "backup1 should be removed"); assert!(!backup2.exists(), "backup2 should be removed"); assert!(keep.exists(), "non-backup file should remain"); } + + // ── test_clean_backups_with_except ──────────────────────────────────────── + + #[test] + fn test_clean_backups_with_except() { + let dir = tempdir().unwrap(); + + let backup1 = dir.path().join("mozart-0.1.0.old"); + let backup2 = dir.path().join("mozart-0.2.0.old"); + + fs::write(&backup1, b"binary").unwrap(); + fs::write(&backup2, b"binary").unwrap(); + + clean_backups(dir.path(), Some(&backup2)).expect("clean_backups should succeed"); + + assert!(!backup1.exists(), "backup1 should be removed"); + assert!(backup2.exists(), "backup2 should be preserved (excepted)"); + } + + // ── test_effective_channel ──────────────────────────────────────────────── + + #[test] + fn test_effective_channel() { + assert_eq!(effective_channel(false), "stable"); + assert_eq!(effective_channel(true), "preview"); + } + + // ── test_version_from_backup ────────────────────────────────────────────── + + #[test] + fn test_version_from_backup() { + let path = PathBuf::from("/some/dir/mozart-0.3.1.old"); + assert_eq!(version_from_backup(&path), "0.3.1"); + + let bad_path = PathBuf::from("/some/dir/unknown-file.txt"); + assert_eq!(version_from_backup(&bad_path), "unknown"); + } } |
