152 lines
4.8 KiB
Rust
152 lines
4.8 KiB
Rust
//! Cleanup-worktrees command: list or remove orphaned worktrees from chat.
|
|
//!
|
|
//! `{bot_name} cleanup_worktrees` lists orphaned worktrees (dry run).
|
|
//! `{bot_name} cleanup_worktrees --confirm` removes them.
|
|
//!
|
|
//! Both paths share the [`crate::worktree::run_cleanup`] handler.
|
|
|
|
use crate::chat::util::strip_bot_mention;
|
|
use crate::config::ProjectConfig;
|
|
use std::path::Path;
|
|
|
|
/// A parsed cleanup_worktrees command from a Matrix message body.
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum CleanupWorktreesCommand {
|
|
/// List orphaned worktrees without removing (dry run).
|
|
DryRun,
|
|
/// Remove all orphaned worktrees.
|
|
Confirm,
|
|
}
|
|
|
|
/// Parse a cleanup_worktrees command from a raw Matrix message body.
|
|
///
|
|
/// Strips the bot mention prefix and checks whether the first word is
|
|
/// `cleanup_worktrees`. Returns `None` when the message is not this command.
|
|
pub fn extract_cleanup_worktrees_command(
|
|
message: &str,
|
|
bot_name: &str,
|
|
bot_user_id: &str,
|
|
) -> Option<CleanupWorktreesCommand> {
|
|
let stripped = strip_bot_mention(message, bot_name, bot_user_id);
|
|
let trimmed = stripped
|
|
.trim()
|
|
.trim_start_matches(|c: char| !c.is_alphanumeric());
|
|
|
|
let (cmd, args) = match trimmed.split_once(char::is_whitespace) {
|
|
Some((c, a)) => (c, a.trim()),
|
|
None => (trimmed, ""),
|
|
};
|
|
|
|
if !cmd.eq_ignore_ascii_case("cleanup_worktrees") {
|
|
return None;
|
|
}
|
|
|
|
if args == "--confirm" {
|
|
Some(CleanupWorktreesCommand::Confirm)
|
|
} else {
|
|
Some(CleanupWorktreesCommand::DryRun)
|
|
}
|
|
}
|
|
|
|
/// Handle a cleanup_worktrees command asynchronously.
|
|
///
|
|
/// Delegates to [`crate::worktree::run_cleanup`] and formats the result as
|
|
/// a Markdown string suitable for posting to the chat room.
|
|
pub async fn handle_cleanup_worktrees(project_root: &Path, confirm: bool) -> String {
|
|
let config = match ProjectConfig::load(project_root) {
|
|
Ok(c) => c,
|
|
Err(e) => return format!("Failed to load project config: {e}"),
|
|
};
|
|
|
|
let report = crate::worktree::run_cleanup(project_root, &config, confirm).await;
|
|
crate::worktree::format_report(&report, confirm)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// -- extract_cleanup_worktrees_command -----------------------------------
|
|
|
|
#[test]
|
|
fn extract_dry_run_with_full_user_id() {
|
|
let cmd = extract_cleanup_worktrees_command(
|
|
"@timmy:home.local cleanup_worktrees",
|
|
"Timmy",
|
|
"@timmy:home.local",
|
|
);
|
|
assert_eq!(cmd, Some(CleanupWorktreesCommand::DryRun));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_confirm_with_display_name() {
|
|
let cmd = extract_cleanup_worktrees_command(
|
|
"Timmy cleanup_worktrees --confirm",
|
|
"Timmy",
|
|
"@timmy:home.local",
|
|
);
|
|
assert_eq!(cmd, Some(CleanupWorktreesCommand::Confirm));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_case_insensitive() {
|
|
let cmd = extract_cleanup_worktrees_command(
|
|
"Timmy CLEANUP_WORKTREES",
|
|
"Timmy",
|
|
"@timmy:home.local",
|
|
);
|
|
assert_eq!(cmd, Some(CleanupWorktreesCommand::DryRun));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_with_localpart() {
|
|
let cmd = extract_cleanup_worktrees_command(
|
|
"@timmy cleanup_worktrees --confirm",
|
|
"Timmy",
|
|
"@timmy:home.local",
|
|
);
|
|
assert_eq!(cmd, Some(CleanupWorktreesCommand::Confirm));
|
|
}
|
|
|
|
#[test]
|
|
fn extract_other_command_returns_none() {
|
|
let cmd =
|
|
extract_cleanup_worktrees_command("Timmy rmtree 42", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, None);
|
|
}
|
|
|
|
#[test]
|
|
fn extract_unrelated_text_returns_none() {
|
|
let cmd = extract_cleanup_worktrees_command("Timmy help", "Timmy", "@timmy:home.local");
|
|
assert_eq!(cmd, None);
|
|
}
|
|
|
|
// -- handle_cleanup_worktrees integration --------------------------------
|
|
|
|
#[tokio::test]
|
|
async fn handle_dry_run_no_worktrees() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
std::fs::create_dir_all(tmp.path().join(".huskies").join("worktrees")).unwrap();
|
|
let response = handle_cleanup_worktrees(tmp.path(), false).await;
|
|
assert!(
|
|
response.contains("No orphaned"),
|
|
"unexpected response: {response}"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn handle_confirm_no_worktrees() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
std::fs::create_dir_all(tmp.path().join(".huskies").join("worktrees")).unwrap();
|
|
let response = handle_cleanup_worktrees(tmp.path(), true).await;
|
|
assert!(
|
|
response.contains("No orphaned"),
|
|
"unexpected response: {response}"
|
|
);
|
|
}
|
|
}
|