Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "migrate"
version = "0.5.0"
version = "0.5.1"
edition = "2021"
description = "Generic file migration tool for applying ordered transformations to a project directory"
license = "MIT"
Expand Down
45 changes: 45 additions & 0 deletions src/commands/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ pub fn run(

if pending.is_empty() {
println!("No pending migrations.");

// Even with no pending migrations, --baseline should clean up stale
// migration files that are at or below the existing baseline version.
if create_baseline && !keep {
if let Some(baseline) = &state.baseline {
let stale: Vec<_> = available
.iter()
.filter(|m| m.version.as_str() <= baseline.version.as_str())
.collect();

if !stale.is_empty() {
if dry_run {
let asset_dir_count = stale
.iter()
.filter(|m| {
m.file_path
.parent()
.map(|p| p.join(&m.id).is_dir())
.unwrap_or(false)
})
.count();
if asset_dir_count > 0 {
println!(
"Would delete {} stale migration file(s) and {} asset directory(ies)",
stale.len(),
asset_dir_count
);
} else {
println!("Would delete {} stale migration file(s)", stale.len());
}
} else {
let deleted = delete_baselined_migrations(&baseline.version, &available)?;
let (files, dirs): (Vec<&DeletedItem>, Vec<&DeletedItem>) =
deleted.iter().partition(|d| !d.is_directory);
if !files.is_empty() {
println!("Deleted {} stale migration file(s)", files.len());
}
if !dirs.is_empty() {
println!("Deleted {} stale asset directory(ies)", dirs.len());
}
}
}
}
}

return Ok(());
}

Expand Down
82 changes: 82 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,85 @@ fn test_status_shows_applied_and_pending() {
assert!(stdout.contains("Pending (1)"));
assert!(stdout.contains("00002-second"));
}

#[test]
fn test_up_baseline_cleans_stale_migrations() {
let temp_dir = create_temp_dir();
let migrations_dir = temp_dir.path().join("migrations");
fs::create_dir(&migrations_dir).unwrap();

// Create a migration and apply it with --baseline
let migration = migrations_dir.join("00001-init.sh");
fs::write(
&migration,
"#!/usr/bin/env bash\nset -euo pipefail\necho init\n",
)
.unwrap();
let mut perms = fs::metadata(&migration).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&migration, perms).unwrap();

let output = Command::new(get_binary_path())
.args([
"--root",
temp_dir.path().to_str().unwrap(),
"up",
"--baseline",
])
.output()
.expect("Failed to execute command");
assert!(output.status.success());

// Migration file should be deleted by baseline
assert!(
!migrations_dir.join("00001-init.sh").exists(),
"Migration file should have been deleted by baseline"
);

// Now simulate the old migration file reappearing (e.g., git merge)
let reappeared = migrations_dir.join("00001-init.sh");
fs::write(
&reappeared,
"#!/usr/bin/env bash\nset -euo pipefail\necho init\n",
)
.unwrap();
let mut perms = fs::metadata(&reappeared).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&reappeared, perms).unwrap();

assert!(
migrations_dir.join("00001-init.sh").exists(),
"Migration file should exist again after simulated reappearance"
);

// Run up --baseline again. No migrations should be applied, but the stale
// file should be cleaned up.
let output = Command::new(get_binary_path())
.args([
"--root",
temp_dir.path().to_str().unwrap(),
"up",
"--baseline",
])
.output()
.expect("Failed to execute command");

let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Should succeed: {}", stdout);
assert!(
stdout.contains("No pending migrations"),
"Should report no pending migrations: {}",
stdout
);
assert!(
stdout.contains("stale migration"),
"Should report cleaning stale migrations: {}",
stdout
);

// The reappeared migration file should be deleted
assert!(
!migrations_dir.join("00001-init.sh").exists(),
"Stale migration file should have been cleaned up"
);
}