Skip to main content

hematite/agent/
git_context.rs

1use std::path::Path;
2use std::process::Command;
3
4/// Reads a short summary of the current git status (branch + changes).
5pub 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
25/// Reads the current git diff (staged + unstaged) and returns it as a formatted string.
26/// Includes capping to prevent token overflow.
27pub fn read_git_diff(cwd: &Path, max_chars: usize) -> Option<String> {
28    let mut sections = Vec::with_capacity(2);
29
30    // 1. Staged changes
31    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    // 2. Unstaged changes
38    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}