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