Skip to main content

cascade_cli/git/
mod.rs

1pub mod branch_manager;
2pub mod conflict_analysis;
3pub mod repository;
4
5pub use branch_manager::{BranchInfo, BranchManager};
6pub use conflict_analysis::{ConflictAnalysis, ConflictAnalyzer, ConflictRegion, ConflictType};
7pub use repository::{GitRepository, GitStatusSummary, RepositoryInfo};
8
9use crate::errors::{CascadeError, Result};
10use std::path::{Path, PathBuf};
11
12/// Resolve the per-worktree git directory from a workdir path.
13/// Handles both normal repos (.git is a directory) and worktrees (.git is a file
14/// containing `gitdir: <path>`).
15pub fn resolve_git_dir(workdir: &Path) -> Result<PathBuf> {
16    let git_path = workdir.join(".git");
17    if git_path.is_dir() {
18        Ok(git_path)
19    } else if git_path.is_file() {
20        let content = std::fs::read_to_string(&git_path)
21            .map_err(|e| CascadeError::config(format!("Failed to read .git file: {e}")))?;
22        let gitdir = content
23            .strip_prefix("gitdir: ")
24            .map(|s| s.trim())
25            .ok_or_else(|| CascadeError::config("Invalid .git file format"))?;
26        let resolved = if std::path::Path::new(gitdir).is_absolute() {
27            PathBuf::from(gitdir)
28        } else {
29            workdir.join(gitdir)
30        };
31        Ok(resolved)
32    } else {
33        Err(CascadeError::config(format!(
34            "Not a git repository: {}",
35            git_path.display()
36        )))
37    }
38}
39
40/// Check if a directory is a Git repository
41pub fn is_git_repository(path: &Path) -> bool {
42    path.join(".git").exists() || git2::Repository::discover(path).is_ok()
43}
44
45/// Find the root of the Git repository
46pub fn find_repository_root(start_path: &Path) -> Result<std::path::PathBuf> {
47    let repo = git2::Repository::discover(start_path).map_err(CascadeError::Git)?;
48
49    let workdir = repo
50        .workdir()
51        .ok_or_else(|| CascadeError::config("Repository has no working directory (bare repo?)"))?;
52
53    Ok(workdir.to_path_buf())
54}
55
56/// Get the current working directory as a Git repository
57pub fn get_current_repository() -> Result<GitRepository> {
58    let current_dir = std::env::current_dir()
59        .map_err(|e| CascadeError::config(format!("Could not get current directory: {e}")))?;
60
61    let repo_root = find_repository_root(&current_dir)?;
62    GitRepository::open(&repo_root)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use std::fs;
69    use tempfile::TempDir;
70
71    #[test]
72    fn test_resolve_git_dir_normal_repo() {
73        let tmp = TempDir::new().unwrap();
74        let git_dir = tmp.path().join(".git");
75        fs::create_dir(&git_dir).unwrap();
76
77        let result = resolve_git_dir(tmp.path()).unwrap();
78        assert_eq!(result, git_dir);
79    }
80
81    #[test]
82    fn test_resolve_git_dir_worktree_absolute() {
83        let tmp = TempDir::new().unwrap();
84        let target = TempDir::new().unwrap();
85        let git_file = tmp.path().join(".git");
86        fs::write(&git_file, format!("gitdir: {}", target.path().display())).unwrap();
87
88        let result = resolve_git_dir(tmp.path()).unwrap();
89        assert_eq!(result, target.path());
90    }
91
92    #[test]
93    fn test_resolve_git_dir_worktree_relative() {
94        let tmp = TempDir::new().unwrap();
95        let target = tmp.path().join("actual_git_dir");
96        fs::create_dir(&target).unwrap();
97        let git_file = tmp.path().join(".git");
98        fs::write(&git_file, "gitdir: actual_git_dir").unwrap();
99
100        let result = resolve_git_dir(tmp.path()).unwrap();
101        assert_eq!(result, tmp.path().join("actual_git_dir"));
102    }
103
104    #[test]
105    fn test_resolve_git_dir_worktree_with_trailing_newline() {
106        let tmp = TempDir::new().unwrap();
107        let target = TempDir::new().unwrap();
108        let git_file = tmp.path().join(".git");
109        fs::write(&git_file, format!("gitdir: {}\n", target.path().display())).unwrap();
110
111        let result = resolve_git_dir(tmp.path()).unwrap();
112        assert_eq!(result, target.path());
113    }
114
115    #[test]
116    fn test_resolve_git_dir_not_a_repo() {
117        let tmp = TempDir::new().unwrap();
118        let result = resolve_git_dir(tmp.path());
119        assert!(result.is_err());
120    }
121
122    #[test]
123    fn test_resolve_git_dir_invalid_git_file() {
124        let tmp = TempDir::new().unwrap();
125        let git_file = tmp.path().join(".git");
126        fs::write(&git_file, "not a valid git file").unwrap();
127
128        let result = resolve_git_dir(tmp.path());
129        assert!(result.is_err());
130    }
131}