diff --git a/src/python.rs b/src/python.rs index 9065088..35756be 100644 --- a/src/python.rs +++ b/src/python.rs @@ -47,8 +47,8 @@ pub struct RepoStatus { // with a call into it. The pure-Rust core is what carries the gix logic + tests. #[pyfunction] -fn is_git_repo(_path: String) -> PyResult { - todo!("repo::is_git_repo (gix::discover)") +fn is_git_repo(path: String) -> PyResult { + Ok(crate::repo::is_git_repo(std::path::Path::new(&path))) } #[pyfunction] diff --git a/src/repo/is_git_repo.rs b/src/repo/is_git_repo.rs new file mode 100644 index 0000000..8ed90fb --- /dev/null +++ b/src/repo/is_git_repo.rs @@ -0,0 +1,86 @@ +use gix::discover; +use std::path::Path; + +/// Returns true iff `path` is inside a git working tree (mirrors +/// `git rev-parse --git-dir` exit==0). +pub fn is_git_repo(path: &Path) -> bool { + discover(path).is_ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::repo::fixtures; + use std::process::Command; + + #[test] + fn repo_root_and_subdir() { + let td = fixtures::repo(); + let root_path = td.path(); + + // Test at the repo root + assert!(is_git_repo(root_path)); + + // Create a subdirectory and test there + let subdir_path = root_path.join("subdir"); + std::fs::create_dir(&subdir_path).expect("mkdir"); + assert!(is_git_repo(&subdir_path)); + } + + #[test] + fn non_repo() { + let td = tempfile::tempdir().expect("tempdir"); + let non_repo_path = td.path(); + assert!(!is_git_repo(non_repo_path)); + } + + #[test] + fn parity_with_git_cli() { + let repo_td = fixtures::repo(); + let repo_path = repo_td.path(); + + // Check the repo path + assert_eq!( + is_git_repo(repo_path), + Command::new("git") + .args(["-C", &repo_path.to_string_lossy(), "rev-parse", "--git-dir"]) + .status() + .expect("spawn git") + .success() + ); + + // Check a subdirectory within the repo + let subdir_path = repo_path.join("subdir"); + std::fs::create_dir(&subdir_path).expect("mkdir"); + assert_eq!( + is_git_repo(&subdir_path), + Command::new("git") + .args([ + "-C", + &subdir_path.to_string_lossy(), + "rev-parse", + "--git-dir" + ]) + .status() + .expect("spawn git") + .success() + ); + + // Check a non-repo path + let non_repo_td = tempfile::tempdir().expect("tempdir"); + let non_repo_path = non_repo_td.path(); + assert_eq!( + is_git_repo(non_repo_path), + Command::new("git") + .args([ + "-C", + &non_repo_path.to_string_lossy(), + "rev-parse", + "--git-dir" + ]) + .status() + .expect("spawn git") + .success() + ); + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index a8b0680..c8edf7d 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -20,6 +20,9 @@ pub use crate::error::{GitxtendError, Result}; // ---- method registrations (one block per implemented method) ------------- // (methods land here as M1 progresses — see docs/ROADMAP.md M1 ordering) +mod is_git_repo; +pub use is_git_repo::is_git_repo; + /// Temp-dir git fixtures shared by the per-method parity tests. /// /// Fixtures are built with the real `git` CLI, so each parity test asserts