gitkraft_core/features/stash/
ops.rs1use anyhow::{Context, Result};
4use git2::Repository;
5use tracing::debug;
6
7use super::types::StashEntry;
8
9pub fn list_stashes(repo: &mut Repository) -> Result<Vec<StashEntry>> {
14 let mut stashes = Vec::new();
15
16 repo.stash_foreach(|index, message, oid| {
17 stashes.push(StashEntry {
18 index,
19 message: message.to_string(),
20 oid: oid.to_string(),
21 });
22 true })
24 .context("Failed to iterate stashes")?;
25
26 debug!("Found {} stash entries", stashes.len());
27 Ok(stashes)
28}
29
30pub fn stash_save(repo: &mut Repository, message: Option<&str>) -> Result<StashEntry> {
35 let signature = repo.signature().context(
36 "Failed to determine default signature for stash — set user.name and user.email",
37 )?;
38
39 let msg = message.unwrap_or("WIP");
40
41 let oid = repo
42 .stash_save(&signature, msg, None)
43 .context("Failed to save stash (are there any changes to stash?)")?;
44
45 debug!("Stash saved: {} — {}", oid, msg);
46
47 Ok(StashEntry {
48 index: 0, message: msg.to_string(),
50 oid: oid.to_string(),
51 })
52}
53
54pub fn stash_pop(repo: &mut Repository, index: usize) -> Result<()> {
56 repo.stash_pop(index, None)
57 .with_context(|| format!("Failed to pop stash at index {index}"))?;
58
59 debug!("Stash at index {} popped", index);
60 Ok(())
61}
62
63pub fn stash_drop(repo: &mut Repository, index: usize) -> Result<()> {
65 repo.stash_drop(index)
66 .with_context(|| format!("Failed to drop stash at index {index}"))?;
67
68 debug!("Stash at index {} dropped", index);
69 Ok(())
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use tempfile::TempDir;
76
77 fn setup_repo_with_commit() -> (TempDir, Repository) {
78 let dir = TempDir::new().unwrap();
80 let repo = Repository::init(dir.path()).unwrap();
81 let mut config = repo.config().unwrap();
82 config.set_str("user.name", "Test").unwrap();
83 config.set_str("user.email", "test@test.com").unwrap();
84 std::fs::write(dir.path().join("file.txt"), "hello\n").unwrap();
85 let mut index = repo.index().unwrap();
86 index.add_path(std::path::Path::new("file.txt")).unwrap();
87 index.write().unwrap();
88 let tree_oid = index.write_tree().unwrap();
89 {
90 let tree = repo.find_tree(tree_oid).unwrap();
91 let sig = repo.signature().unwrap();
92 repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
93 .unwrap();
94 }
95 (dir, repo)
96 }
97
98 #[test]
99 fn list_stashes_empty() {
100 let (_dir, mut repo) = setup_repo_with_commit();
101 let stashes = list_stashes(&mut repo).unwrap();
102 assert!(stashes.is_empty());
103 }
104
105 #[test]
106 fn stash_save_and_list() {
107 let (dir, mut repo) = setup_repo_with_commit();
108 std::fs::write(dir.path().join("file.txt"), "changed\n").unwrap();
110 let entry = stash_save(&mut repo, Some("test stash")).unwrap();
111 assert_eq!(entry.index, 0);
112 assert!(entry.message.contains("test stash"));
113
114 let stashes = list_stashes(&mut repo).unwrap();
115 assert_eq!(stashes.len(), 1);
116 }
117
118 #[test]
119 fn stash_pop_restores_changes() {
120 let (dir, mut repo) = setup_repo_with_commit();
121 std::fs::write(dir.path().join("file.txt"), "changed\n").unwrap();
122 stash_save(&mut repo, Some("test")).unwrap();
123
124 let content = std::fs::read_to_string(dir.path().join("file.txt")).unwrap();
126 assert_eq!(content, "hello\n");
127
128 stash_pop(&mut repo, 0).unwrap();
129 let content = std::fs::read_to_string(dir.path().join("file.txt")).unwrap();
130 assert_eq!(content, "changed\n");
131 }
132}