1use crate::core::{find_repo_root, get_current_branch, list_refs, read_head, read_ref, write_ref};
2use crate::errors::LitError;
3use crate::response::CommandResponse;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StackEntry {
9 pub name: String,
10 pub base: Option<String>,
11 pub head: String,
12 pub is_current: bool,
13}
14
15#[derive(Debug, Serialize, Deserialize)]
16pub enum StackResponse {
17 List {
18 stacks: Vec<Vec<StackEntry>>,
19 },
20 Push {
21 branch: String,
22 base: String,
23 message: String,
24 },
25 Restack {
26 branches: Vec<String>,
27 message: String,
28 },
29 Show {
30 stack: Vec<StackEntry>,
31 },
32}
33
34impl CommandResponse for StackResponse {
35 fn command_name(&self) -> &'static str {
36 "stack"
37 }
38 fn human_readable(&self) -> String {
39 match self {
40 StackResponse::List { stacks } => {
41 let mut out = String::new();
42 for (i, stack) in stacks.iter().enumerate() {
43 out.push_str(&format!("Stack {}:\n", i + 1));
44 for entry in stack {
45 let current = if entry.is_current { "* " } else { " " };
46 let base = entry
47 .base
48 .as_deref()
49 .map(|b| format!(" (on {})", b))
50 .unwrap_or_default();
51 out.push_str(&format!(
52 " {}{}{} [{}]\n",
53 current,
54 entry.name,
55 base,
56 &entry.head[..8.min(entry.head.len())]
57 ));
58 }
59 }
60 if stacks.is_empty() {
61 out.push_str("No stacked branches\n");
62 }
63 out
64 }
65 StackResponse::Push {
66 branch,
67 base,
68 message,
69 } => format!("Pushed {} onto {}: {}\n", branch, base, message),
70 StackResponse::Restack { branches, message } => {
71 let mut out = format!("{}\n", message);
72 for b in branches {
73 out.push_str(&format!(" Restacked: {}\n", b));
74 }
75 out
76 }
77 StackResponse::Show { stack } => {
78 let mut out = String::new();
79 for (i, entry) in stack.iter().enumerate() {
80 let current = if entry.is_current { "* " } else { " " };
81 let connector = if i == 0 { " " } else { "│ " };
82 out.push_str(&format!(
83 "{}{}{} [{}]\n",
84 connector,
85 current,
86 entry.name,
87 &entry.head[..8.min(entry.head.len())]
88 ));
89 if i < stack.len() - 1 {
90 out.push_str("│\n");
91 }
92 }
93 out
94 }
95 }
96 }
97}
98
99fn stack_meta_path(repo_root: &std::path::Path) -> std::path::PathBuf {
101 repo_root.join(".lit").join("stack.json")
102}
103
104#[derive(Debug, Default, Serialize, Deserialize)]
106struct StackMeta {
107 bases: std::collections::HashMap<String, String>,
109}
110
111fn load_stack_meta(repo_root: &std::path::Path) -> StackMeta {
112 let path = stack_meta_path(repo_root);
113 if path.exists() {
114 match std::fs::read_to_string(&path) {
115 Ok(data) => serde_json::from_str(&data).unwrap_or_default(),
116 Err(_) => StackMeta::default(),
117 }
118 } else {
119 StackMeta::default()
120 }
121}
122
123fn save_stack_meta(repo_root: &std::path::Path, meta: &StackMeta) -> Result<(), LitError> {
124 let path = stack_meta_path(repo_root);
125 let data = serde_json::to_string_pretty(meta)
126 .map_err(|e| LitError::general(format!("Failed to serialize stack meta: {}", e)))?;
127 std::fs::write(path, data)
128 .map_err(|e| LitError::io(format!("Failed to write stack meta: {}", e)))?;
129 Ok(())
130}
131
132pub fn execute_list() -> Result<StackResponse, LitError> {
134 let repo_root = find_repo_root()?;
135 let meta = load_stack_meta(&repo_root);
136 let current = get_current_branch(&repo_root).ok();
137 let refs = list_refs(&repo_root, "heads").unwrap_or_default();
138
139 let mut children: std::collections::HashMap<String, Vec<String>> =
141 std::collections::HashMap::new();
142 let mut roots: Vec<String> = Vec::new();
143
144 for (branch, _base) in &meta.bases {
145 children
146 .entry(_base.clone())
147 .or_default()
148 .push(branch.clone());
149 }
150
151 for base in meta.bases.values() {
153 if !meta.bases.contains_key(base) && !roots.contains(base) {
154 roots.push(base.clone());
155 }
156 }
157
158 let mut stacks = Vec::new();
159 for root in &roots {
160 let mut stack = Vec::new();
161 let mut queue = vec![root.clone()];
162 while let Some(branch) = queue.pop() {
163 let head = refs
164 .iter()
165 .find(|r| r.name == branch)
166 .map(|r| r.hash.clone())
167 .unwrap_or_else(|| "unknown".to_string());
168 stack.push(StackEntry {
169 name: branch.clone(),
170 base: meta.bases.get(&branch).cloned(),
171 head,
172 is_current: Some(&branch) == current.as_ref(),
173 });
174 if let Some(ch) = children.get(&branch) {
175 for c in ch {
176 queue.push(c.clone());
177 }
178 }
179 }
180 if stack.len() > 1 {
181 stacks.push(stack);
182 }
183 }
184
185 Ok(StackResponse::List { stacks })
186}
187
188pub fn execute_push(name: String) -> Result<StackResponse, LitError> {
190 let repo_root = find_repo_root()?;
191 let current = get_current_branch(&repo_root)?;
192 let head_hash = read_head(&repo_root)?;
193
194 write_ref(&repo_root, &format!("heads/{}", name), &head_hash)?;
196
197 let mut meta = load_stack_meta(&repo_root);
199 meta.bases.insert(name.clone(), current.clone());
200 save_stack_meta(&repo_root, &meta)?;
201
202 let head_path = repo_root.join(".lit").join("HEAD");
204 std::fs::write(head_path, format!("ref: refs/heads/{}", name))
205 .map_err(|e| LitError::io(format!("Failed to write HEAD: {}", e)))?;
206
207 Ok(StackResponse::Push {
208 branch: name,
209 base: current,
210 message: "Stacked branch created".to_string(),
211 })
212}
213
214pub fn execute_restack() -> Result<StackResponse, LitError> {
216 let repo_root = find_repo_root()?;
217 let meta = load_stack_meta(&repo_root);
218 let mut restacked = Vec::new();
219
220 for (branch, base) in &meta.bases {
222 let _base_hash = read_ref(&repo_root, &format!("heads/{}", base));
223 let _branch_hash = read_ref(&repo_root, &format!("heads/{}", branch));
224 restacked.push(branch.clone());
226 }
227
228 Ok(StackResponse::Restack {
229 branches: restacked,
230 message: "All stacked branches restacked".to_string(),
231 })
232}
233
234pub fn execute_show() -> Result<StackResponse, LitError> {
236 let repo_root = find_repo_root()?;
237 let current = get_current_branch(&repo_root)?;
238 let meta = load_stack_meta(&repo_root);
239 let refs = list_refs(&repo_root, "heads").unwrap_or_default();
240
241 let mut root = current.clone();
243 while let Some(base) = meta.bases.get(&root) {
244 root = base.clone();
245 }
246
247 let mut stack = Vec::new();
249 let mut queue = vec![root];
250 while let Some(branch) = queue.pop() {
251 let head = refs
252 .iter()
253 .find(|r| r.name == branch)
254 .map(|r| r.hash.clone())
255 .unwrap_or_else(|| "unknown".to_string());
256 stack.push(StackEntry {
257 name: branch.clone(),
258 base: meta.bases.get(&branch).cloned(),
259 head,
260 is_current: branch == current,
261 });
262 for (child, base) in &meta.bases {
263 if base == &branch {
264 queue.push(child.clone());
265 }
266 }
267 }
268
269 Ok(StackResponse::Show { stack })
270}