use std::path::Path;
use std::process::{Command, Stdio};
pub const GIT_CONFIG_FLAGS: &[&str] = &[
"-c",
"core.fsmonitor=false",
"-c",
"core.hooksPath=/dev/null",
];
pub fn secure_git() -> Command {
let mut cmd = Command::new("git");
cmd.args(GIT_CONFIG_FLAGS);
cmd
}
pub fn working_tree_dirty(repo: &Path) -> bool {
secure_git()
.args(["status", "--porcelain"])
.current_dir(repo)
.stderr(Stdio::null())
.output()
.is_ok_and(|o| o.status.success() && !o.stdout.is_empty())
}
pub fn has_unpushed_commits(repo: &Path) -> bool {
let output = secure_git()
.args(["rev-list", "--count", "@{u}..HEAD"])
.current_dir(repo)
.stderr(Stdio::null())
.output();
let Ok(output) = output else { return false };
if !output.status.success() {
return false;
}
let count = std::str::from_utf8(&output.stdout)
.unwrap_or("0")
.trim()
.parse::<u32>()
.unwrap_or(0);
count > 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secure_git_includes_config_flags() {
let cmd = secure_git();
assert_eq!(cmd.get_program(), "git");
}
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
#[cfg(test)]
mod prop_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn parse_rev_list_count_handles_valid_numeric_output(count_val in 0u32..1000) {
let output_bytes = format!("{}", count_val).into_bytes();
let parsed = std::str::from_utf8(&output_bytes)
.unwrap_or("0")
.trim()
.parse::<u32>()
.unwrap_or(0);
prop_assert_eq!(parsed, count_val);
}
#[test]
fn parse_rev_list_count_handles_malformed_output(junk in ".*") {
let _parsed = std::str::from_utf8(junk.as_bytes())
.unwrap_or("0")
.trim()
.parse::<u32>()
.unwrap_or(0);
}
#[test]
fn parse_rev_list_count_with_whitespace(count_val in 0u32..1000) {
let output_bytes = format!(" {} \n", count_val).into_bytes();
let parsed = std::str::from_utf8(&output_bytes)
.unwrap_or("0")
.trim()
.parse::<u32>()
.unwrap_or(0);
prop_assert_eq!(parsed, count_val);
}
}
}
}