hematite/agent/
git_context.rs1use std::path::Path;
2use std::process::Command;
3
4pub fn read_git_status(cwd: &Path) -> Option<String> {
6 let output = Command::new("git")
7 .args(["--no-optional-locks", "status", "--short", "--branch"])
8 .current_dir(cwd)
9 .output()
10 .ok()?;
11
12 if !output.status.success() {
13 return None;
14 }
15
16 let stdout = String::from_utf8(output.stdout).ok()?;
17 let trimmed = stdout.trim();
18 if trimmed.is_empty() {
19 None
20 } else {
21 Some(trimmed.to_string())
22 }
23}
24
25pub fn read_git_diff(cwd: &Path, max_chars: usize) -> Option<String> {
28 let mut sections = Vec::new();
29
30 if let Some(staged) = read_git_output(cwd, &["diff", "--cached"]) {
32 if !staged.trim().is_empty() {
33 sections.push(format!("Staged changes:\n{}", staged.trim_end()));
34 }
35 }
36
37 if let Some(unstaged) = read_git_output(cwd, &["diff"]) {
39 if !unstaged.trim().is_empty() {
40 sections.push(format!("Unstaged changes:\n{}", unstaged.trim_end()));
41 }
42 }
43
44 if sections.is_empty() {
45 None
46 } else {
47 let combined = sections.join("\n\n");
48 if combined.len() > max_chars {
49 Some(format!(
50 "{}\n... [diff capped at {} chars]",
51 &combined[..max_chars],
52 max_chars
53 ))
54 } else {
55 Some(combined)
56 }
57 }
58}
59
60fn read_git_output(cwd: &Path, args: &[&str]) -> Option<String> {
61 let output = Command::new("git")
62 .args(args)
63 .current_dir(cwd)
64 .output()
65 .ok()?;
66
67 if !output.status.success() {
68 return None;
69 }
70
71 String::from_utf8(output.stdout).ok()
72}