use anyhow::{Result, bail};
use std::path::Path;
use std::process::Command;
use super::git_output_in;
pub fn is_git_dirty() -> bool {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
is_git_dirty_in(&cwd)
}
pub fn is_git_dirty_in(cwd: &Path) -> bool {
git_output_in(cwd, &["status", "--porcelain"])
.map(|s| !s.is_empty())
.unwrap_or(false)
}
pub fn local_git_user_name() -> Option<String> {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
local_git_user_name_in(&cwd)
}
pub fn local_git_user_name_in(cwd: &Path) -> Option<String> {
git_output_in(cwd, &["config", "user.name"])
.ok()
.filter(|s| !s.is_empty())
}
pub fn local_git_user_email() -> Option<String> {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
local_git_user_email_in(&cwd)
}
pub fn local_git_user_email_in(cwd: &Path) -> Option<String> {
git_output_in(cwd, &["config", "user.email"])
.ok()
.filter(|s| !s.is_empty())
}
pub fn check_git_available() -> Result<()> {
let output = Command::new("git").arg("--version").output();
match output {
Ok(o) if o.status.success() => Ok(()),
_ => bail!("git is not installed or not in PATH. Install git and try again."),
}
}
pub fn is_git_repo() -> bool {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
is_git_repo_in(&cwd)
}
pub fn is_git_repo_in(cwd: &Path) -> bool {
git_output_in(cwd, &["rev-parse", "--git-dir"]).is_ok()
}
pub fn git_status_porcelain() -> String {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
git_status_porcelain_in(&cwd)
}
pub fn git_status_porcelain_in(cwd: &Path) -> String {
git_output_in(cwd, &["status", "--porcelain"]).unwrap_or_default()
}
pub fn is_shallow_clone() -> bool {
let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
is_shallow_clone_in(&cwd)
}
pub fn is_shallow_clone_in(cwd: &Path) -> bool {
let git_dir =
git_output_in(cwd, &["rev-parse", "--git-dir"]).unwrap_or_else(|_| ".git".to_string());
let git_dir_path = Path::new(&git_dir);
let shallow = if git_dir_path.is_absolute() {
git_dir_path.join("shallow")
} else {
cwd.join(git_dir_path).join("shallow")
};
shallow.exists()
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
fn init_repo(dir: &Path) {
let run = |args: &[&str]| {
let out = Command::new("git")
.args(args)
.current_dir(dir)
.env("GIT_AUTHOR_NAME", "test")
.env("GIT_AUTHOR_EMAIL", "test@test.com")
.env("GIT_COMMITTER_NAME", "test")
.env("GIT_COMMITTER_EMAIL", "test@test.com")
.output()
.unwrap();
assert!(
out.status.success(),
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&out.stderr)
);
};
run(&["init"]);
run(&["config", "user.email", "test@test.com"]);
run(&["config", "user.name", "Status Tester"]);
std::fs::write(dir.join("README"), "init").unwrap();
run(&["add", "."]);
run(&["commit", "-m", "initial"]);
}
#[test]
fn is_git_repo_in_returns_false_for_non_git_dir() {
let tmp = tempfile::tempdir().unwrap();
assert!(!is_git_repo_in(tmp.path()));
}
#[test]
fn is_git_repo_in_returns_true_for_initialized_repo() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
assert!(is_git_repo_in(tmp.path()));
}
#[test]
fn is_git_dirty_in_is_false_for_clean_repo() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
assert!(!is_git_dirty_in(tmp.path()));
}
#[test]
fn is_git_dirty_in_is_true_after_untracked_change() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
std::fs::write(tmp.path().join("new.txt"), "hello").unwrap();
assert!(is_git_dirty_in(tmp.path()));
}
#[test]
fn git_status_porcelain_in_reflects_dirty_state() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
std::fs::write(tmp.path().join("staged.txt"), "x").unwrap();
let status = git_status_porcelain_in(tmp.path());
assert!(status.contains("staged.txt"), "got: {status:?}");
}
#[test]
fn local_git_user_name_in_reads_repo_config() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
assert_eq!(
local_git_user_name_in(tmp.path()).as_deref(),
Some("Status Tester")
);
}
#[test]
fn local_git_user_email_in_reads_repo_config() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
assert_eq!(
local_git_user_email_in(tmp.path()).as_deref(),
Some("test@test.com")
);
}
#[test]
fn is_shallow_clone_in_is_false_for_full_clone() {
let tmp = tempfile::tempdir().unwrap();
init_repo(tmp.path());
assert!(!is_shallow_clone_in(tmp.path()));
}
}