Skip to main content

git_worktree_manager/operations/
stash.rs

1/// Stash operations for git-worktree-manager.
2///
3/// Mirrors src/git_worktree_manager/operations/stash_ops.py.
4use std::collections::HashMap;
5
6use console::style;
7
8use crate::error::{CwError, Result};
9use crate::git;
10
11/// Save changes in current worktree to stash.
12pub 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    // Check for changes
22    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
47/// List all stashes organized by worktree/branch.
48pub 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        // Extract branch name from our format [branch-name] or from git format
72        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
113/// Apply a stash to a different worktree.
114pub 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    // Verify stash exists
130    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}