1use std::path::Path;
4use std::process::Command;
5
6use crate::domain::error::{AivcsError, Result};
7
8pub fn capture_head_sha(repo_dir: &Path) -> Result<String> {
13 let output = Command::new("git")
14 .args(["rev-parse", "HEAD"])
15 .current_dir(repo_dir)
16 .output()
17 .map_err(|e| AivcsError::GitError(format!("failed to run git: {e}")))?;
18
19 if !output.status.success() {
20 let stderr = String::from_utf8_lossy(&output.stderr);
21 return Err(AivcsError::GitError(format!(
22 "git rev-parse HEAD failed: {stderr}"
23 )));
24 }
25
26 let sha = String::from_utf8_lossy(&output.stdout).trim().to_string();
27 if sha.is_empty() {
28 return Err(AivcsError::GitError(
29 "git rev-parse HEAD returned empty output".to_string(),
30 ));
31 }
32
33 Ok(sha)
34}
35
36pub fn is_git_repo(dir: &Path) -> bool {
38 Command::new("git")
39 .args(["rev-parse", "--is-inside-work-tree"])
40 .current_dir(dir)
41 .output()
42 .map(|o| o.status.success())
43 .unwrap_or(false)
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49 use std::path::Path;
50 use std::process::Command as StdCommand;
51
52 fn run_git(repo_dir: &Path, args: &[&str]) {
53 let output = StdCommand::new("git")
54 .args(args)
55 .current_dir(repo_dir)
56 .output()
57 .unwrap();
58 assert!(
59 output.status.success(),
60 "git {:?} failed: {}",
61 args,
62 String::from_utf8_lossy(&output.stderr)
63 );
64 }
65
66 fn make_git_repo() -> tempfile::TempDir {
67 let dir = tempfile::tempdir().unwrap();
68 run_git(dir.path(), &["init"]);
69 run_git(dir.path(), &["config", "user.name", "test-user"]);
70 run_git(dir.path(), &["config", "user.email", "test@example.com"]);
71 run_git(dir.path(), &["commit", "--allow-empty", "-m", "initial"]);
72 dir
73 }
74
75 #[test]
76 fn capture_head_sha_returns_40_hex_chars() {
77 let repo = make_git_repo();
78 let sha = capture_head_sha(repo.path()).unwrap();
79 assert_eq!(sha.len(), 40, "SHA should be 40 hex chars, got: {sha}");
80 assert!(sha.chars().all(|c| c.is_ascii_hexdigit()));
81 }
82
83 #[test]
84 fn capture_head_sha_fails_outside_repo() {
85 let dir = tempfile::tempdir().unwrap();
86 let result = capture_head_sha(dir.path());
87 assert!(result.is_err());
88 }
89
90 #[test]
91 fn is_git_repo_true_for_repo() {
92 let repo = make_git_repo();
93 assert!(is_git_repo(repo.path()));
94 }
95
96 #[test]
97 fn is_git_repo_false_for_non_repo() {
98 let dir = tempfile::tempdir().unwrap();
99 assert!(!is_git_repo(dir.path()));
100 }
101}