Skip to main content

deepseek_rust_cli/tools/
git_ops.rs

1use anyhow::Result;
2use tokio::process::Command;
3
4// ─── Local Git Operations ───────────────────────────────────────────
5
6pub async fn git_status(path: Option<&str>) -> Result<String> {
7    let p = path.unwrap_or(".");
8    let output = Command::new("git")
9        .arg("-C")
10        .arg(p)
11        .arg("status")
12        .arg("-s")
13        .output()
14        .await?;
15
16    if !output.status.success() {
17        return Ok(format!(
18            "Git error: {}",
19            String::from_utf8_lossy(&output.stderr)
20        ));
21    }
22
23    let out = String::from_utf8_lossy(&output.stdout).to_string();
24    if out.is_empty() {
25        Ok("Working tree clean.".to_string())
26    } else {
27        Ok(out)
28    }
29}
30
31pub async fn git_diff(path: Option<&str>, staged: bool) -> Result<String> {
32    let p = path.unwrap_or(".");
33    let mut cmd = Command::new("git");
34    cmd.arg("-C").arg(p).arg("diff");
35    if staged {
36        cmd.arg("--staged");
37    }
38    let output = cmd.output().await?;
39
40    if !output.status.success() {
41        return Ok(format!(
42            "Git error: {}",
43            String::from_utf8_lossy(&output.stderr)
44        ));
45    }
46
47    let out = String::from_utf8_lossy(&output.stdout).to_string();
48    if out.is_empty() {
49        Ok("No changes.".to_string())
50    } else {
51        Ok(out)
52    }
53}
54
55pub async fn git_log(path: Option<&str>, count: Option<usize>) -> Result<String> {
56    let p = path.unwrap_or(".");
57    let n = count.unwrap_or(10);
58    let output = Command::new("git")
59        .arg("-C")
60        .arg(p)
61        .arg("log")
62        .arg(format!("-n{}", n))
63        .arg("--oneline")
64        .arg("--decorate")
65        .output()
66        .await?;
67
68    if !output.status.success() {
69        return Ok(format!(
70            "Git error: {}",
71            String::from_utf8_lossy(&output.stderr)
72        ));
73    }
74    Ok(String::from_utf8_lossy(&output.stdout).to_string())
75}
76
77pub async fn git_branch(
78    path: Option<&str>,
79    action: Option<&str>,
80    name: Option<&str>,
81) -> Result<String> {
82    let p = path.unwrap_or(".");
83    let mut cmd = Command::new("git");
84    cmd.arg("-C").arg(p);
85
86    match action {
87        Some("create") => {
88            if let Some(n) = name {
89                cmd.arg("branch").arg(n);
90                let output = cmd.output().await?;
91                if !output.status.success() {
92                    return Ok(format!(
93                        "Git error: {}",
94                        String::from_utf8_lossy(&output.stderr)
95                    ));
96                }
97                return Ok(format!("Branch '{}' created.", n));
98            }
99        }
100        Some("delete") => {
101            if let Some(n) = name {
102                cmd.arg("branch").arg("-d").arg(n);
103                let output = cmd.output().await?;
104                if !output.status.success() {
105                    return Ok(format!(
106                        "Git error: {}",
107                        String::from_utf8_lossy(&output.stderr)
108                    ));
109                }
110                return Ok(format!("Branch '{}' deleted.", n));
111            }
112        }
113        Some("switch") => {
114            if let Some(n) = name {
115                cmd.arg("checkout").arg(n);
116                let output = cmd.output().await?;
117                if !output.status.success() {
118                    return Ok(format!(
119                        "Git error: {}",
120                        String::from_utf8_lossy(&output.stderr)
121                    ));
122                }
123                return Ok(format!("Switched to branch '{}'.", n));
124            }
125        }
126        _ => {
127            // List branches (default)
128            cmd.arg("branch").arg("--list");
129            let output = cmd.output().await?;
130            if !output.status.success() {
131                return Ok(format!(
132                    "Git error: {}",
133                    String::from_utf8_lossy(&output.stderr)
134                ));
135            }
136            return Ok(String::from_utf8_lossy(&output.stdout).to_string());
137        }
138    }
139
140    Ok("No action specified.".to_string())
141}
142
143pub async fn git_add(path: Option<&str>, files: Option<&str>) -> Result<String> {
144    let p = path.unwrap_or(".");
145    let targets = files.unwrap_or(".");
146    let output = Command::new("git")
147        .arg("-C")
148        .arg(p)
149        .arg("add")
150        .arg(targets)
151        .output()
152        .await?;
153
154    if !output.status.success() {
155        return Ok(format!(
156            "Git error: {}",
157            String::from_utf8_lossy(&output.stderr)
158        ));
159    }
160    Ok(format!("Staged: {}", targets))
161}
162
163pub async fn git_commit(path: Option<&str>, message: &str) -> Result<String> {
164    let p = path.unwrap_or(".");
165    let output = Command::new("git")
166        .arg("-C")
167        .arg(p)
168        .arg("commit")
169        .arg("-m")
170        .arg(message)
171        .output()
172        .await?;
173
174    if !output.status.success() {
175        return Ok(format!(
176            "Git error: {}",
177            String::from_utf8_lossy(&output.stderr)
178        ));
179    }
180    let out = String::from_utf8_lossy(&output.stdout).to_string();
181    Ok(if out.is_empty() {
182        "Committed successfully.".to_string()
183    } else {
184        out
185    })
186}
187
188pub async fn git_push(
189    path: Option<&str>,
190    remote: Option<&str>,
191    branch: Option<&str>,
192) -> Result<String> {
193    let p = path.unwrap_or(".");
194    let r = remote.unwrap_or("origin");
195    let mut cmd = Command::new("git");
196    cmd.arg("-C").arg(p).arg("push").arg(r);
197    if let Some(b) = branch {
198        cmd.arg(b);
199    }
200
201    let output = cmd.output().await?;
202    if !output.status.success() {
203        return Ok(format!(
204            "Git error: {}",
205            String::from_utf8_lossy(&output.stderr)
206        ));
207    }
208    let out = String::from_utf8_lossy(&output.stdout).to_string();
209    let err = String::from_utf8_lossy(&output.stderr).to_string();
210    Ok(format!("{}{}", out, err))
211}
212
213pub async fn git_pull(
214    path: Option<&str>,
215    remote: Option<&str>,
216    branch: Option<&str>,
217) -> Result<String> {
218    let p = path.unwrap_or(".");
219    let r = remote.unwrap_or("origin");
220    let mut cmd = Command::new("git");
221    cmd.arg("-C").arg(p).arg("pull").arg(r);
222    if let Some(b) = branch {
223        cmd.arg(b);
224    }
225
226    let output = cmd.output().await?;
227    if !output.status.success() {
228        return Ok(format!(
229            "Git error: {}",
230            String::from_utf8_lossy(&output.stderr)
231        ));
232    }
233    let out = String::from_utf8_lossy(&output.stdout).to_string();
234    let err = String::from_utf8_lossy(&output.stderr).to_string();
235    Ok(format!("{}{}", out, err))
236}
237
238pub async fn git_checkout(path: Option<&str>, target: &str) -> Result<String> {
239    let p = path.unwrap_or(".");
240    let output = Command::new("git")
241        .arg("-C")
242        .arg(p)
243        .arg("checkout")
244        .arg(target)
245        .output()
246        .await?;
247
248    if !output.status.success() {
249        return Ok(format!(
250            "Git error: {}",
251            String::from_utf8_lossy(&output.stderr)
252        ));
253    }
254    Ok(format!("Checked out: {}", target))
255}
256
257pub async fn git_clone(url: &str, dest: Option<&str>) -> Result<String> {
258    let mut cmd = Command::new("git");
259    cmd.arg("clone").arg(url);
260    if let Some(d) = dest {
261        cmd.arg(d);
262    }
263
264    let output = cmd.output().await?;
265    if !output.status.success() {
266        return Ok(format!(
267            "Git error: {}",
268            String::from_utf8_lossy(&output.stderr)
269        ));
270    }
271    let out = String::from_utf8_lossy(&output.stdout).to_string();
272    let err = String::from_utf8_lossy(&output.stderr).to_string();
273    Ok(format!("{}{}", out, err))
274}
275
276pub async fn git_remote_list(path: Option<&str>) -> Result<String> {
277    let p = path.unwrap_or(".");
278    let output = Command::new("git")
279        .arg("-C")
280        .arg(p)
281        .arg("remote")
282        .arg("-v")
283        .output()
284        .await?;
285
286    if !output.status.success() {
287        return Ok(format!(
288            "Git error: {}",
289            String::from_utf8_lossy(&output.stderr)
290        ));
291    }
292    Ok(String::from_utf8_lossy(&output.stdout).to_string())
293}
294
295pub async fn git_stash(path: Option<&str>, action: Option<&str>) -> Result<String> {
296    let p = path.unwrap_or(".");
297    let mut cmd = Command::new("git");
298    cmd.arg("-C").arg(p).arg("stash");
299
300    match action {
301        Some("pop") => {
302            cmd.arg("pop");
303        }
304        Some("list") => {
305            cmd.arg("list");
306        }
307        _ => {} // default: stash (save)
308    }
309
310    let output = cmd.output().await?;
311    if !output.status.success() {
312        return Ok(format!(
313            "Git error: {}",
314            String::from_utf8_lossy(&output.stderr)
315        ));
316    }
317    Ok(String::from_utf8_lossy(&output.stdout).to_string())
318}