git_worktree_manager/operations/
stash.rs1use std::collections::HashMap;
4
5use console::style;
6
7use crate::error::{CwError, Result};
8use crate::git;
9use crate::messages;
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('[') {
73 if let Some(end) = stash_msg.find(']') {
74 stash_msg[1..end].to_string()
75 } else {
76 "unknown".to_string()
77 }
78 } else if stash_info.contains("On ") {
79 stash_info
80 .split("On ")
81 .nth(1)
82 .unwrap_or("unknown")
83 .trim()
84 .to_string()
85 } else if stash_info.contains("WIP on ") {
86 stash_info
87 .split("WIP on ")
88 .nth(1)
89 .unwrap_or("unknown")
90 .trim()
91 .to_string()
92 } else {
93 "unknown".to_string()
94 };
95
96 by_branch
97 .entry(branch_name)
98 .or_default()
99 .push((stash_ref.to_string(), stash_msg.to_string()));
100 }
101
102 let mut branches: Vec<_> = by_branch.keys().cloned().collect();
103 branches.sort();
104
105 for branch in branches {
106 println!("{}:", style(&branch).green().bold());
107 for (stash_ref, msg) in &by_branch[&branch] {
108 println!(" {}: {}", stash_ref, msg);
109 }
110 println!();
111 }
112
113 Ok(())
114}
115
116pub fn stash_apply(target_branch: &str, stash_ref: &str) -> Result<()> {
118 let repo = git::get_repo_root(None)?;
119
120 let wt_path = git::find_worktree_by_branch(&repo, target_branch)?
121 .or(git::find_worktree_by_branch(
122 &repo,
123 &format!("refs/heads/{}", target_branch),
124 )?)
125 .ok_or_else(|| CwError::WorktreeNotFound(messages::worktree_not_found(target_branch)))?;
126
127 let verify = git::git_command(&["stash", "list"], Some(&repo), false, true)?;
129 if !verify.stdout.contains(stash_ref) {
130 return Err(CwError::Git(messages::stash_not_found(stash_ref)));
131 }
132
133 println!(
134 "\n{}",
135 style(format!("Applying {} to {}...", stash_ref, target_branch)).yellow()
136 );
137
138 match git::git_command(&["stash", "apply", stash_ref], Some(&wt_path), false, true) {
139 Ok(r) if r.returncode == 0 => {
140 println!(
141 "{} Stash applied to {}\n",
142 style("*").green().bold(),
143 target_branch
144 );
145 println!(
146 "{}\n",
147 style(format!("Worktree path: {}", wt_path.display())).dim()
148 );
149 }
150 _ => {
151 println!("{} Failed to apply stash\n", style("x").red().bold());
152 println!(
153 "{}\n",
154 style("Tip: There may be conflicts. Check the worktree and resolve manually.")
155 .yellow()
156 );
157 }
158 }
159
160 Ok(())
161}