Skip to main content

deepseek_rust_cli/tools/
git_ops.rs

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