huskies: merge 1029

This commit is contained in:
dave
2026-05-14 12:45:56 +00:00
parent 0a45805f7b
commit a80d0a497a
4 changed files with 293 additions and 16 deletions
+59
View File
@@ -88,3 +88,62 @@ pub async fn run_git_owned(args: Vec<String>, dir: PathBuf) -> Result<Output, Er
.map_err(|e| Error::UpstreamFailure(format!("Task join error: {e}")))?
.map_err(|e| Error::Io(format!("Failed to run git: {e}")))
}
/// Summary of uncommitted changes in a git working tree.
///
/// Returned by [`read_dirty_files_sync`] for use in status rendering.
#[derive(Debug, Default)]
pub struct DirtyFiles {
/// Number of tracked files that have uncommitted changes (staged or unstaged).
pub modified: usize,
/// Number of untracked files.
pub new: usize,
/// All dirty file paths in the order `git status` reports them (deduplicated).
pub paths: Vec<String>,
}
impl DirtyFiles {
/// Returns `true` when the working tree has no uncommitted changes.
pub fn is_clean(&self) -> bool {
self.paths.is_empty()
}
}
/// Run `git status --porcelain=v1 -u` synchronously in `dir` and return dirty file info.
///
/// Returns a zeroed [`DirtyFiles`] if `dir` is not a git repo, git is unavailable, or the
/// working tree is clean.
pub fn read_dirty_files_sync(dir: &Path) -> DirtyFiles {
let output = match std::process::Command::new("git")
.args(["status", "--porcelain=v1", "-u"])
.current_dir(dir)
.output()
{
Ok(o) => o,
Err(_) => return DirtyFiles::default(),
};
if !output.status.success() && output.stdout.is_empty() {
return DirtyFiles::default();
}
let stdout = String::from_utf8_lossy(&output.stdout);
let (staged, unstaged, untracked) = super::porcelain::parse_git_status_porcelain(&stdout);
let mut seen = std::collections::HashSet::new();
let mut paths = Vec::new();
let mut modified_set = std::collections::HashSet::new();
for path in staged.iter().chain(unstaged.iter()) {
modified_set.insert(path.clone());
if seen.insert(path.clone()) {
paths.push(path.clone());
}
}
for path in &untracked {
if seen.insert(path.clone()) {
paths.push(path.clone());
}
}
DirtyFiles {
modified: modified_set.len(),
new: untracked.len(),
paths,
}
}
+1
View File
@@ -14,6 +14,7 @@ pub mod path_guard;
/// Pure git porcelain output parsers.
pub mod porcelain;
pub use io::DirtyFiles;
#[allow(unused_imports)]
pub use path_guard::is_under_root;
pub use porcelain::parse_git_status_porcelain;