add backlog delete command
diff --git a/src/bin/ranger/commands/backlog.rs b/src/bin/ranger/commands/backlog.rs
index a9cfab4..a611656 100644
--- a/src/bin/ranger/commands/backlog.rs
+++ b/src/bin/ranger/commands/backlog.rs
@@ -32,6 +32,13 @@ pub enum BacklogCommands {
#[arg(long)]
done: bool,
},
+ /// Delete a backlog and all its tasks
+ #[command(visible_alias = "rm")]
+ Delete {
+ /// Backlog name
+ #[arg(add = ArgValueCompleter::new(completions::complete_backlog_names))]
+ name: String,
+ },
/// Rebalance task positions in a backlog
Rebalance {
/// Backlog name
@@ -52,6 +59,10 @@ pub async fn run(pool: &SqlitePool, command: BacklogCommands, json: bool) -> Res
let backlogs = ops::backlog::list(&mut conn).await?;
output::print_list(&backlogs, json, print_backlog);
}
+ BacklogCommands::Delete { name } => {
+ let backlog = ops::backlog::delete(&mut conn, &name).await?;
+ output::print(&backlog, json, |b| println!("Deleted backlog: {}", b.name));
+ }
BacklogCommands::Rebalance { name } => {
let backlog = ops::backlog::get_by_name(&mut conn, &name).await?;
let count = ops::task::rebalance(&mut conn, backlog.id).await?;
diff --git a/src/ops/backlog.rs b/src/ops/backlog.rs
index 8466557..a5a239f 100644
--- a/src/ops/backlog.rs
+++ b/src/ops/backlog.rs
@@ -32,6 +32,15 @@ pub async fn get_by_name(conn: &mut SqliteConnection, name: &str) -> Result<Back
Ok(backlog)
}
+pub async fn delete(conn: &mut SqliteConnection, name: &str) -> Result<Backlog, RangerError> {
+ let backlog = get_by_name(&mut *conn, name).await?;
+ sqlx::query("DELETE FROM backlogs WHERE id = ?")
+ .bind(backlog.id)
+ .execute(&mut *conn)
+ .await?;
+ Ok(backlog)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -73,4 +82,24 @@ mod tests {
let result = get_by_name(&mut conn, "nonexistent").await;
assert!(result.is_err());
}
+
+ #[tokio::test]
+ async fn delete_backlog() {
+ let pool = test_pool().await;
+ let mut conn = pool.acquire().await.unwrap();
+ create(&mut conn, "ToDelete").await.unwrap();
+ let deleted = delete(&mut conn, "ToDelete").await.unwrap();
+ assert_eq!(deleted.name, "ToDelete");
+
+ let result = get_by_name(&mut conn, "ToDelete").await;
+ assert!(result.is_err());
+ }
+
+ #[tokio::test]
+ async fn delete_backlog_not_found() {
+ let pool = test_pool().await;
+ let mut conn = pool.acquire().await.unwrap();
+ let result = delete(&mut conn, "nonexistent").await;
+ assert!(result.is_err());
+ }
}
diff --git a/tests/cli.rs b/tests/cli.rs
index 885dbfc..40d223b 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -421,6 +421,46 @@ fn full_workflow() {
assert!(!stdout.contains("bug"));
assert!(stdout.contains("frontend"));
+ // --- Backlog delete ---
+
+ // Create a throwaway backlog with a task, then delete it
+ ranger(db_path)
+ .args(["backlog", "create", "Throwaway"])
+ .output()
+ .unwrap();
+ ranger(db_path)
+ .args(["task", "create", "Doomed task", "--backlog", "Throwaway"])
+ .output()
+ .unwrap();
+ let output = ranger(db_path)
+ .args(["backlog", "delete", "Throwaway"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("Deleted backlog: Throwaway"));
+
+ // Verify backlog is gone
+ let output = ranger(db_path)
+ .args(["backlog", "list", "--json"])
+ .output()
+ .unwrap();
+ let backlogs: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+ let names: Vec<&str> = backlogs
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|b| b["name"].as_str().unwrap())
+ .collect();
+ assert!(!names.contains(&"Throwaway"));
+
+ // Deleting non-existent backlog fails
+ let output = ranger(db_path)
+ .args(["backlog", "delete", "Nonexistent"])
+ .output()
+ .unwrap();
+ assert!(!output.status.success());
+
// Dynamic shell completions via COMPLETE env var
for shell in ["bash", "zsh", "fish", "elvish", "powershell"] {
let output = ranger(db_path).env("COMPLETE", shell).output().unwrap();