Skip to main content

git_worktree_manager/operations/
stash.rs

1/// Stash operations for git-worktree-manager.
2///
3use std::collections::HashMap;
4
5use console::style;
6
7use crate::error::{CwError, Result};
8use crate::git;
9use crate::messages;
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('[') {
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
116/// Apply a stash to a different worktree.
117pub 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    // Verify stash exists
128    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}