commit_bridge/polling/
git.rs1use crate::{domain::CommitHash, error::CommitHashError};
4use async_trait::async_trait;
5
6#[async_trait]
8pub trait GitFetcher: Send + Sync {
9 async fn get_latest_hash(
11 &self,
12 repo_url: &str,
13 branch: &str,
14 ) -> Result<CommitHash, CommitHashError>;
15}
16
17pub struct MainGitFetcher;
19
20#[async_trait]
21impl GitFetcher for MainGitFetcher {
22 async fn get_latest_hash(
23 &self,
24 repo_url: &str,
25 branch: &str,
26 ) -> Result<CommitHash, CommitHashError> {
27 tokio::process::Command::new("git")
28 .args(["ls-remote", "--", repo_url, branch])
29 .output()
30 .await
31 .map_err(CommitHashError::from)
32 .and_then(handle_git_output_result)
33 .and_then(|stdout| extract_hash(stdout, repo_url.to_string(), branch.to_string()))
34 }
35}
36
37fn handle_git_output_result(output: std::process::Output) -> Result<Vec<u8>, CommitHashError> {
39 if output.status.success() {
40 Ok(output.stdout)
41 } else {
42 let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
43 Err(CommitHashError::UnexpectedStatus(stderr))
44 }
45}
46
47fn extract_hash(
49 stdout: Vec<u8>,
50 repo_url: String,
51 branch: String,
52) -> Result<CommitHash, CommitHashError> {
53 let hash_str = String::from_utf8_lossy(&stdout)
54 .split_whitespace()
55 .next()
56 .map(|s| s.to_string())
57 .ok_or(CommitHashError::UnexpectedOutput {
58 stdout: String::from_utf8_lossy(&stdout).into_owned(),
59 repo_url,
60 branch,
61 })?;
62
63 Ok(CommitHash::new(hash_str)?)
64}
65
66#[cfg(test)]
67mod tests {
68 #![allow(
69 clippy::panic,
70 clippy::expect_used,
71 clippy::todo,
72 clippy::unimplemented,
73 clippy::indexing_slicing
74 )]
75
76 use super::*;
77
78 #[test]
79 fn test_extract_hash_success() {
80 let stdout = b"678c4343237127dbadbf1806dd98b2154ffd2ebe\trefs/heads/main\n".to_vec();
81 let repo_url = "https://github.com/Nilirad/commit-bridge".to_string();
82 let branch = "main".to_string();
83
84 let result = extract_hash(stdout, repo_url, branch);
85 assert!(result.is_ok());
86 assert_eq!(
87 result.unwrap().as_str(),
88 "678c4343237127dbadbf1806dd98b2154ffd2ebe"
89 );
90 }
91
92 #[test]
93 fn test_extract_hash_empty_output() {
94 let stdout = b"".to_vec();
95 let repo_url = "https://github.com/Nilirad/commit-bridge".to_string();
96 let branch = "main".to_string();
97
98 let result = extract_hash(stdout.clone(), repo_url.clone(), branch.clone());
99
100 let Err(CommitHashError::UnexpectedOutput {
101 stdout: err_stdout,
102 repo_url: err_repo_url,
103 branch: err_branch,
104 }) = result
105 else {
106 panic!("Expected UnexpectedOutput error");
107 };
108
109 assert_eq!(err_stdout, String::from_utf8(stdout).unwrap_or_default());
110 assert_eq!(err_repo_url, repo_url);
111 assert_eq!(err_branch, branch);
112 }
113
114 #[test]
115 fn test_extract_hash_whitespace_only() {
116 let stdout = b" \n\t ".to_vec();
117 let repo_url = "https://github.com/Nilirad/commit-bridge".to_string();
118 let branch = "main".to_string();
119
120 let result = extract_hash(stdout.clone(), repo_url.clone(), branch.clone());
121
122 let Err(CommitHashError::UnexpectedOutput {
123 stdout: err_stdout,
124 repo_url: err_repo_url,
125 branch: err_branch,
126 }) = result
127 else {
128 panic!("Expected UnexpectedOutput error");
129 };
130
131 assert_eq!(err_stdout, String::from_utf8(stdout).unwrap_or_default());
132 assert_eq!(err_repo_url, repo_url);
133 assert_eq!(err_branch, branch);
134 }
135}