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
12pub 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
40pub fn is_git_repository(path: &Path) -> bool {
42 path.join(".git").exists() || git2::Repository::discover(path).is_ok()
43}
44
45pub 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
56pub 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(¤t_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}