Files
pkh/src/new/git.rs
T

100 lines
3.5 KiB
Rust

//! 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<bool, Box<dyn std::error::Error>> {
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());
}
}