cc-hook 0.1.0

A cross-platform CLI that installs a git commit-msg hook to enforce Conventional Commits
use std::path::{Path, PathBuf};

/// Searches for a `.git` directory starting from `start_dir` and walking up parent directories.
///
/// Returns the path to the repository root (the directory containing `.git`), or `None` if
/// no `.git` directory is found before reaching the filesystem root.
pub fn find_git_root(start_dir: &Path) -> Option<PathBuf> {
    let mut current = start_dir.to_path_buf();

    loop {
        if current.join(".git").is_dir() {
            return Some(current);
        }

        if !current.pop() {
            return None;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    /// Helper: creates a temporary directory structure for testing.
    fn create_temp_dir() -> tempfile::TempDir {
        tempfile::tempdir().expect("failed to create temp dir")
    }

    #[test]
    fn finds_git_dir_in_current_directory() {
        let tmp = create_temp_dir();
        let git_dir = tmp.path().join(".git");
        fs::create_dir(&git_dir).unwrap();

        let result = find_git_root(tmp.path());
        assert_eq!(result, Some(tmp.path().to_path_buf()));
    }

    #[test]
    fn finds_git_dir_in_parent_directory() {
        let tmp = create_temp_dir();
        let git_dir = tmp.path().join(".git");
        fs::create_dir(&git_dir).unwrap();

        let child = tmp.path().join("src").join("components");
        fs::create_dir_all(&child).unwrap();

        let result = find_git_root(&child);
        assert_eq!(result, Some(tmp.path().to_path_buf()));
    }

    #[test]
    fn finds_git_dir_in_grandparent_directory() {
        let tmp = create_temp_dir();
        let git_dir = tmp.path().join(".git");
        fs::create_dir(&git_dir).unwrap();

        let deep = tmp.path().join("a").join("b").join("c");
        fs::create_dir_all(&deep).unwrap();

        let result = find_git_root(&deep);
        assert_eq!(result, Some(tmp.path().to_path_buf()));
    }

    #[test]
    fn returns_none_when_no_git_dir() {
        let tmp = create_temp_dir();
        // No .git directory created

        let result = find_git_root(tmp.path());
        assert_eq!(result, None);
    }

    #[test]
    fn stops_at_closest_git_root() {
        let tmp = create_temp_dir();

        // Outer repo
        fs::create_dir(tmp.path().join(".git")).unwrap();

        // Inner repo (submodule-like)
        let inner = tmp.path().join("packages").join("sub");
        fs::create_dir_all(&inner).unwrap();
        fs::create_dir(inner.join(".git")).unwrap();

        let deep = inner.join("src");
        fs::create_dir_all(&deep).unwrap();

        let result = find_git_root(&deep);
        assert_eq!(result, Some(inner.clone()));
    }
}