cascade_cli/cli/commands/
cleanup.rs

1use crate::cli::output::Output;
2use crate::errors::{CascadeError, Result};
3use crate::git::GitRepository;
4use chrono::{DateTime, Utc};
5use std::env;
6
7/// Run the cleanup command to remove orphaned temporary branches
8pub async fn run(execute: bool, force: bool) -> Result<()> {
9    let repo_path = env::current_dir()
10        .map_err(|e| CascadeError::config(format!("Failed to get current directory: {e}")))?;
11
12    let git_repo = GitRepository::open(&repo_path)?;
13
14    Output::section("๐Ÿงน Scanning for orphaned temporary branches");
15
16    // Find all branches matching temp pattern: *-temp-*
17    let all_branches = git_repo.list_branches()?;
18    let temp_branches: Vec<String> = all_branches
19        .iter()
20        .filter(|b| b.contains("-temp-"))
21        .cloned()
22        .collect();
23
24    if temp_branches.is_empty() {
25        Output::success("โœ“ No orphaned temporary branches found");
26        return Ok(());
27    }
28
29    Output::info(format!("Found {} temporary branches:", temp_branches.len()));
30
31    // Analyze each temp branch
32    for branch_name in &temp_branches {
33        let branch_info = analyze_temp_branch(&git_repo, branch_name)?;
34
35        Output::sub_item(format!("  {} {}", branch_name, branch_info));
36    }
37
38    println!(); // Blank line
39
40    if !execute {
41        Output::warning("๐Ÿ” DRY RUN MODE - No branches will be deleted");
42        Output::info("Run with --execute to actually delete these branches");
43        Output::info("Use --force to delete branches with unmerged commits");
44        return Ok(());
45    }
46
47    // Actually delete the branches
48    Output::section(format!(
49        "Deleting {} temporary branches...",
50        temp_branches.len()
51    ));
52
53    let mut deleted = 0;
54    let mut failed = 0;
55
56    for branch_name in &temp_branches {
57        match git_repo.delete_branch_unsafe(branch_name) {
58            Ok(_) => {
59                Output::success(format!("โœ“ Deleted: {}", branch_name));
60                deleted += 1;
61            }
62            Err(e) if !force => {
63                Output::warning(format!("โš ๏ธ  Skipped: {} ({})", branch_name, e));
64                Output::sub_item("   Use --force to delete branches with unmerged commits");
65                failed += 1;
66            }
67            Err(e) => {
68                Output::error(format!("โœ— Failed to delete: {} ({})", branch_name, e));
69                failed += 1;
70            }
71        }
72    }
73
74    println!(); // Blank line
75
76    if deleted > 0 {
77        Output::success(format!("โœ“ Successfully deleted {} branches", deleted));
78    }
79
80    if failed > 0 {
81        Output::warning(format!("โš ๏ธ  {} branches could not be deleted", failed));
82    }
83
84    Ok(())
85}
86
87/// Analyze a temporary branch and return info about it
88fn analyze_temp_branch(git_repo: &GitRepository, branch_name: &str) -> Result<String> {
89    // Try to extract timestamp from branch name (format: *-temp-1234567890)
90    let parts: Vec<&str> = branch_name.split("-temp-").collect();
91
92    if parts.len() == 2 {
93        if let Ok(timestamp) = parts[1].parse::<i64>() {
94            if let Some(created_at) = DateTime::from_timestamp(timestamp, 0) {
95                let now = Utc::now();
96                let age = now.signed_duration_since(created_at);
97
98                if age.num_days() > 0 {
99                    return Ok(format!("(created {} days ago)", age.num_days()));
100                } else if age.num_hours() > 0 {
101                    return Ok(format!("(created {} hours ago)", age.num_hours()));
102                } else {
103                    return Ok(format!("(created {} minutes ago)", age.num_minutes()));
104                }
105            }
106        }
107    }
108
109    // Try to get last commit info
110    match git_repo.get_branch_commit_hash(branch_name) {
111        Ok(commit_hash) => Ok(format!("(commit: {})", &commit_hash[..8])),
112        Err(_) => Ok("(orphaned)".to_string()),
113    }
114}