1use crate::core::{find_repo_root, get_current_branch, list_refs};
2use crate::errors::LitError;
3use crate::response::CommandResponse;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Serialize, Deserialize)]
7pub struct CleanResponse {
8 pub removed: Vec<String>,
9 pub message: String,
10}
11
12impl CommandResponse for CleanResponse {
13 fn command_name(&self) -> &'static str {
14 "clean"
15 }
16 fn human_readable(&self) -> String {
17 if self.removed.is_empty() {
18 "No empty branches to clean\n".to_string()
19 } else {
20 let mut out = format!("{}\n", self.message);
21 for b in &self.removed {
22 out.push_str(&format!(" Removed: {}\n", b));
23 }
24 out
25 }
26 }
27}
28
29pub fn execute(dry_run: bool) -> Result<CleanResponse, LitError> {
33 let repo_root = find_repo_root()?;
34 let current = get_current_branch(&repo_root)?;
35 let refs = list_refs(&repo_root, "heads").unwrap_or_default();
36
37 let stack_meta_path = repo_root.join(".lit").join("stack.json");
39 let stack_bases: std::collections::HashMap<String, String> = if stack_meta_path.exists() {
40 #[derive(Deserialize)]
41 struct SM {
42 bases: std::collections::HashMap<String, String>,
43 }
44 match std::fs::read_to_string(&stack_meta_path) {
45 Ok(data) => serde_json::from_str::<SM>(&data)
46 .map(|s| s.bases)
47 .unwrap_or_default(),
48 Err(_) => std::collections::HashMap::new(),
49 }
50 } else {
51 std::collections::HashMap::new()
52 };
53
54 let mut to_remove = Vec::new();
56 let default_branch_hash = refs
57 .iter()
58 .find(|r| r.name == "main" || r.name == "master")
59 .map(|r| r.hash.clone());
60
61 for r in &refs {
62 if r.name == current {
63 continue; }
65 if r.name == "main" || r.name == "master" {
66 continue; }
68
69 let is_empty = if let Some(base_name) = stack_bases.get(&r.name) {
70 refs.iter()
72 .find(|br| br.name == *base_name)
73 .map(|br| br.hash == r.hash)
74 .unwrap_or(false)
75 } else if let Some(ref dh) = default_branch_hash {
76 &r.hash == dh
78 } else {
79 false
80 };
81
82 if is_empty {
83 to_remove.push(r.name.clone());
84 }
85 }
86
87 if !dry_run {
88 for name in &to_remove {
89 let _ = crate::core::delete_ref(&repo_root, &format!("heads/{}", name));
90 }
91 }
92
93 let msg = if dry_run {
94 format!("Would remove {} empty branch(es)", to_remove.len())
95 } else {
96 format!("Removed {} empty branch(es)", to_remove.len())
97 };
98
99 Ok(CleanResponse {
100 removed: to_remove,
101 message: msg,
102 })
103}