1use std::path::{Path, PathBuf};
4
5pub mod amendment;
6pub mod commit;
7pub mod diff_split;
8pub mod main_branches;
9pub mod remote;
10pub mod repository;
11pub mod worktree_rebase;
12
13pub use amendment::AmendmentHandler;
14pub use commit::{
15 refine_message_scope, resolve_scope, CommitAnalysis, CommitAnalysisForAI, CommitInfo,
16 CommitInfoForAI, FileDiffRef,
17};
18pub use diff_split::{split_by_file, split_file_by_hunk, FileDiff, HunkDiff};
19pub use main_branches::{branches_containing, detect_main_branch_tips, MainBranchTip};
20pub use remote::RemoteInfo;
21pub use repository::GitRepository;
22
23pub const SHORT_HASH_LEN: usize = 8;
25
26pub const FULL_HASH_LEN: usize = 40;
28
29const GIT_BIN_ENV: &str = "OMNI_DEV_GIT_BIN";
34
35const GIT_BINARY_CANDIDATES: &[&str] = &[
46 "/opt/homebrew/bin/git",
47 "/usr/local/bin/git",
48 "/home/linuxbrew/.linuxbrew/bin/git",
49 "/usr/bin/git",
50];
51
52#[must_use]
65pub fn resolve_git_binary() -> PathBuf {
66 resolve_git_binary_from(std::env::var_os(GIT_BIN_ENV), GIT_BINARY_CANDIDATES)
67}
68
69fn resolve_git_binary_from(
72 env_override: Option<std::ffi::OsString>,
73 candidates: &[&str],
74) -> PathBuf {
75 if let Some(path) = env_override.filter(|p| !p.is_empty()) {
76 return PathBuf::from(path);
77 }
78 for candidate in candidates {
79 let path = Path::new(candidate);
80 if path.exists() {
81 return path.to_path_buf();
82 }
83 }
84 PathBuf::from("git")
85}
86
87#[cfg(test)]
88#[allow(clippy::unwrap_used)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn resolve_git_binary_from_prefers_env_then_candidate_then_fallback() {
94 assert_eq!(
95 resolve_git_binary_from(Some("/custom/git".into()), &["/usr/bin/git"]),
96 PathBuf::from("/custom/git"),
97 "an explicit override wins over every candidate"
98 );
99 let existing = std::env::current_exe().unwrap();
101 let existing = existing.to_str().unwrap();
102 assert_eq!(
103 resolve_git_binary_from(None, &["/no/such/git/xyzzy", existing]),
104 PathBuf::from(existing),
105 "the first *existing* candidate wins, not merely the first"
106 );
107 assert_eq!(
108 resolve_git_binary_from(None, &["/no/such/git/xyzzy"]),
109 PathBuf::from("git"),
110 "with nothing found, fall back to a bare PATH lookup"
111 );
112 assert_eq!(
113 resolve_git_binary_from(Some(String::new().into()), &["/no/such/git/xyzzy"]),
114 PathBuf::from("git"),
115 "an empty override is ignored rather than spawning \"\""
116 );
117 }
118
119 #[test]
120 fn resolve_git_binary_reads_the_real_environment() {
121 let _ = resolve_git_binary();
123 }
124}