git_worktree_manager/operations/
stash.rs1use std::collections::HashMap;
5
6use console::style;
7
8use crate::error::{CwError, Result};
9use crate::git;
10
11pub fn stash_save(message: Option<&str>) -> Result<()> {
13 let cwd = std::env::current_dir()?;
14 let branch = git::get_current_branch(Some(&cwd))?;
15
16 let stash_msg = match message {
17 Some(m) => format!("[{}] {}", branch, m),
18 None => format!("[{}] WIP", branch),
19 };
20
21 let status = git::git_command(&["status", "--porcelain"], Some(&cwd), false, true)?;
23 if status.returncode == 0 && status.stdout.trim().is_empty() {
24 println!("{} No changes to stash\n", style("!").yellow());
25 return Ok(());
26 }
27
28 println!(
29 "{}",
30 style(format!("Stashing changes in {}...", branch)).yellow()
31 );
32 git::git_command(
33 &["stash", "push", "--include-untracked", "-m", &stash_msg],
34 Some(&cwd),
35 true,
36 false,
37 )?;
38 println!(
39 "{} Stashed changes: {}\n",
40 style("*").green().bold(),
41 stash_msg
42 );
43
44 Ok(())
45}
46
47pub fn stash_list() -> Result<()> {
49 let repo = git::get_repo_root(None)?;
50 let result = git::git_command(&["stash", "list"], Some(&repo), false, true)?;
51
52 if result.stdout.trim().is_empty() {
53 println!("{}\n", style("No stashes found").yellow());
54 return Ok(());
55 }
56
57 println!("\n{}\n", style("Stashes by worktree:").cyan().bold());
58
59 let mut by_branch: HashMap<String, Vec<(String, String)>> = HashMap::new();
60
61 for line in result.stdout.trim().lines() {
62 let parts: Vec<&str> = line.splitn(3, ':').collect();
63 if parts.len() < 3 {
64 continue;
65 }
66
67 let stash_ref = parts[0].trim();
68 let stash_info = parts[1].trim();
69 let stash_msg = parts[2].trim();
70
71 let branch_name = if stash_msg.starts_with('[') && stash_msg.contains(']') {
73 let end = stash_msg.find(']').unwrap();
74 stash_msg[1..end].to_string()
75 } else if stash_info.contains("On ") {
76 stash_info
77 .split("On ")
78 .nth(1)
79 .unwrap_or("unknown")
80 .trim()
81 .to_string()
82 } else if stash_info.contains("WIP on ") {
83 stash_info
84 .split("WIP on ")
85 .nth(1)
86 .unwrap_or("unknown")
87 .trim()
88 .to_string()
89 } else {
90 "unknown".to_string()
91 };
92
93 by_branch
94 .entry(branch_name)
95 .or_default()
96 .push((stash_ref.to_string(), stash_msg.to_string()));
97 }
98
99 let mut branches: Vec<_> = by_branch.keys().cloned().collect();
100 branches.sort();
101
102 for branch in branches {
103 println!("{}:", style(&branch).green().bold());
104 for (stash_ref, msg) in &by_branch[&branch] {
105 println!(" {}: {}", stash_ref, msg);
106 }
107 println!();
108 }
109
110 Ok(())
111}
112
113pub fn stash_apply(target_branch: &str, stash_ref: &str) -> Result<()> {
115 let repo = git::get_repo_root(None)?;
116
117 let wt_path = git::find_worktree_by_branch(&repo, target_branch)?
118 .or(git::find_worktree_by_branch(
119 &repo,
120 &format!("refs/heads/{}", target_branch),
121 )?)
122 .ok_or_else(|| {
123 CwError::WorktreeNotFound(format!(
124 "No worktree found for branch '{}'. Use 'cw list' to see available worktrees.",
125 target_branch
126 ))
127 })?;
128
129 let verify = git::git_command(&["stash", "list"], Some(&repo), false, true)?;
131 if !verify.stdout.contains(stash_ref) {
132 return Err(CwError::Git(format!(
133 "Stash '{}' not found. Use 'cw stash list' to see available stashes.",
134 stash_ref
135 )));
136 }
137
138 println!(
139 "\n{}",
140 style(format!("Applying {} to {}...", stash_ref, target_branch)).yellow()
141 );
142
143 match git::git_command(&["stash", "apply", stash_ref], Some(&wt_path), false, true) {
144 Ok(r) if r.returncode == 0 => {
145 println!(
146 "{} Stash applied to {}\n",
147 style("*").green().bold(),
148 target_branch
149 );
150 println!(
151 "{}\n",
152 style(format!("Worktree path: {}", wt_path.display())).dim()
153 );
154 }
155 _ => {
156 println!("{} Failed to apply stash\n", style("x").red().bold());
157 println!(
158 "{}\n",
159 style("Tip: There may be conflicts. Check the worktree and resolve manually.")
160 .yellow()
161 );
162 }
163 }
164
165 Ok(())
166}