//! Git handling for `pkh new`: initialize a repository in the scaffolded //! tree unless it is already inside one (the `.gitignore`s are written //! regardless). use std::path::Path; /// Ensure `dir` has a git repository when it should: when `dir` is already /// inside a work tree (its own or a parent's), nothing is initialized and /// `Ok(false)` is returned with an info log; otherwise a repository is /// initialized in `dir` when `init` is set (the `--no-git` case passes /// `init = false`). pub fn ensure_repository(dir: &Path, init: bool) -> Result> { match git2::Repository::discover(dir) { Ok(_) => { log::info!( "Already inside a git repository; skipping git init \ (the .gitignore files are written anyway)" ); Ok(false) } Err(_) if init => { git2::Repository::init(dir)?; log::info!("Initialized empty git repository in {}", dir.display()); Ok(true) } // --no-git: gitignores only. Err(_) => Ok(false), } } /// Whether `dir` sits inside a git work tree (its own or a parent's), in the /// spirit of `git rev-parse --is-inside-work-tree` (like /// [`crate::new::origin::GitOrigin::detect`]). Fail-soft: when `git` cannot /// be run or the probe fails, `dir` counts as outside — the caller keeps the /// behavior it would have without the probe, and the scaffold-time /// repository discovery in [`ensure_repository`] has the final word anyway. pub fn inside_work_tree(dir: &Path) -> bool { let Ok(output) = std::process::Command::new("git") .args(["rev-parse", "--is-inside-work-tree"]) .current_dir(dir) .output() else { return false; }; output.status.success() && String::from_utf8_lossy(&output.stdout).trim() == "true" } #[cfg(test)] mod tests { use super::*; use tempfile::tempdir; #[test] fn init_skipped_with_no_git() { let dir = tempdir().unwrap(); assert!(!ensure_repository(dir.path(), false).unwrap()); assert!(!dir.path().join(".git").exists()); } #[test] fn init_creates_repository() { let dir = tempdir().unwrap(); assert!(ensure_repository(dir.path(), true).unwrap()); assert!(dir.path().join(".git").exists()); // A second call discovers the fresh repository and skips init. assert!(!ensure_repository(dir.path(), true).unwrap()); } #[test] fn parent_repository_is_discovered() { let dir = tempdir().unwrap(); let sub = dir.path().join("sub"); std::fs::create_dir_all(&sub).unwrap(); git2::Repository::init(dir.path()).unwrap(); // The subdirectory is already inside the parent work tree. assert!(!ensure_repository(&sub, true).unwrap()); assert!(!sub.join(".git").exists()); } /// The probe answers like `git rev-parse --is-inside-work-tree`: /// outside any repository it says no, inside one (at any depth, as a /// skeleton mode scaffold would probe through its parent directory) it /// says yes without touching the tree. #[test] fn inside_work_tree_follows_the_parent_repository() { let dir = tempdir().unwrap(); assert!(!inside_work_tree(dir.path())); git2::Repository::init(dir.path()).unwrap(); assert!(inside_work_tree(dir.path())); let sub = dir.path().join("sub"); std::fs::create_dir(&sub).unwrap(); assert!(inside_work_tree(&sub)); assert!(!sub.join(".git").exists()); } }