diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-23 12:10:44 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-23 12:14:11 +0900 |
| commit | d8ecb21a7931ec6f1d7b447d0c15f53de32bfc45 (patch) | |
| tree | 0da1e0c2ec988905b98939b524b72217e83019de /crates/mozart-vcs | |
| parent | 0080efea9386d46f65d1862fcb90eb44999d9761 (diff) | |
| download | php-mozart-d8ecb21a7931ec6f1d7b447d0c15f53de32bfc45.tar.gz php-mozart-d8ecb21a7931ec6f1d7b447d0c15f53de32bfc45.tar.zst php-mozart-d8ecb21a7931ec6f1d7b447d0c15f53de32bfc45.zip | |
refactor(vcs): convert VcsDriver trait to native async
Replace manual tokio::runtime::Handle::current().block_on() calls with
native async/await throughout all VCS drivers. Introduce AnyVcsDriver
enum for static dispatch to avoid dyn trait with async methods.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-vcs')
| -rw-r--r-- | crates/mozart-vcs/src/driver/bitbucket.rs | 64 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/forgejo.rs | 58 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/git.rs | 21 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/github.rs | 61 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/gitlab.rs | 64 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/hg.rs | 21 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/mod.rs | 131 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/driver/svn.rs | 21 | ||||
| -rw-r--r-- | crates/mozart-vcs/src/repository.rs | 24 | ||||
| -rw-r--r-- | crates/mozart-vcs/tests/git_driver_test.rs | 32 |
10 files changed, 302 insertions, 195 deletions
diff --git a/crates/mozart-vcs/src/driver/bitbucket.rs b/crates/mozart-vcs/src/driver/bitbucket.rs index 9a0fc15..dc7d2cf 100644 --- a/crates/mozart-vcs/src/driver/bitbucket.rs +++ b/crates/mozart-vcs/src/driver/bitbucket.rs @@ -62,8 +62,7 @@ impl BitbucketDriver { ) } - fn api_get(&self, path: &str) -> Result<serde_json::Value> { - let handle = tokio::runtime::Handle::current(); + async fn api_get(&self, path: &str) -> Result<serde_json::Value> { let url = self.api_url(path); let mut req = self .http_client @@ -76,7 +75,7 @@ impl BitbucketDriver { req = req.header(AUTHORIZATION, format!("Basic {credentials}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { bail!( "Bitbucket API request to {} failed: {}", @@ -84,11 +83,10 @@ impl BitbucketDriver { response.status() ); } - Ok(handle.block_on(response.json())?) + Ok(response.json().await?) } - fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { - let handle = tokio::runtime::Handle::current(); + async fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { let mut items = Vec::new(); let mut next_url = Some(self.api_url(path)); let mut pages = 0; @@ -102,11 +100,11 @@ impl BitbucketDriver { if let Some((key, secret)) = &self.config.bitbucket_oauth { req = req.header(AUTHORIZATION, format!("Basic {key}:{secret}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { break; } - let data: serde_json::Value = handle.block_on(response.json())?; + let data: serde_json::Value = response.json().await?; if let Some(values) = data["values"].as_array() { items.extend(values.iter().cloned()); } @@ -119,11 +117,11 @@ impl BitbucketDriver { Ok(items) } - fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { + async fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { if self.git_driver.is_none() { let git_url = format!("https://bitbucket.org/{}/{}.git", self.owner, self.repo); let mut driver = GitDriver::new(&git_url, self.config.clone()); - driver.initialize()?; + driver.initialize().await?; self.git_driver = Some(Box::new(driver)); } Ok(self.git_driver.as_mut().unwrap()) @@ -131,8 +129,8 @@ impl BitbucketDriver { } impl VcsDriver for BitbucketDriver { - fn initialize(&mut self) -> Result<()> { - match self.api_get("") { + async fn initialize(&mut self) -> Result<()> { + match self.api_get("").await { Ok(data) => { if let Some(scm) = data["scm"].as_str() { self.vcs_type = scm.to_string(); @@ -145,7 +143,7 @@ impl VcsDriver for BitbucketDriver { } Err(_) => { self.api_failed = true; - let driver = self.use_git_fallback()?; + let driver = self.use_git_fallback().await?; self.root_identifier = Some(driver.root_identifier().to_string()); } } @@ -156,14 +154,14 @@ impl VcsDriver for BitbucketDriver { self.root_identifier.as_deref().unwrap_or("main") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let branches = driver.branches()?.clone(); + let driver = self.use_git_fallback().await?; + let branches = driver.branches().await?.clone(); self.branches = Some(branches); } else { - let items = self.api_get_paginated("/refs/branches?pagelen=100")?; + let items = self.api_get_paginated("/refs/branches?pagelen=100").await?; let mut branches = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -178,14 +176,14 @@ impl VcsDriver for BitbucketDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let tags = driver.tags()?.clone(); + let driver = self.use_git_fallback().await?; + let tags = driver.tags().await?.clone(); self.tags = Some(tags); } else { - let items = self.api_get_paginated("/refs/tags?pagelen=100")?; + let items = self.api_get_paginated("/refs/tags?pagelen=100").await?; let mut tags = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -200,46 +198,48 @@ impl VcsDriver for BitbucketDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = content.and_then(|c| serde_json::from_str(&c).ok()); self.info_cache .insert(identifier.to_string(), value.clone()); Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } - let handle = tokio::runtime::Handle::current(); let url = self.api_url(&format!("/src/{identifier}/{file}")); let mut req = self.http_client.get(&url).header(USER_AGENT, "mozart/0.1"); if let Some((key, secret)) = &self.config.bitbucket_oauth { req = req.header(AUTHORIZATION, format!("Basic {key}:{secret}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if response.status().is_success() { - Ok(Some(handle.block_on(response.text())?)) + Ok(Some(response.text().await?)) } else { Ok(None) } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } - match self.api_get(&format!("/commit/{identifier}")) { + match self.api_get(&format!("/commit/{identifier}")).await { Ok(data) => Ok(data["date"].as_str().map(|s| s.to_string())), Err(_) => Ok(None), } } - fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { Ok(Some(DistReference { dist_type: "zip".to_string(), url: format!( @@ -263,9 +263,9 @@ impl VcsDriver for BitbucketDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { if let Some(driver) = &mut self.git_driver { - driver.cleanup()?; + driver.cleanup().await?; } Ok(()) } diff --git a/crates/mozart-vcs/src/driver/forgejo.rs b/crates/mozart-vcs/src/driver/forgejo.rs index f35f9df..0447422 100644 --- a/crates/mozart-vcs/src/driver/forgejo.rs +++ b/crates/mozart-vcs/src/driver/forgejo.rs @@ -76,8 +76,7 @@ impl ForgejoDriver { ) } - fn api_get(&self, path: &str) -> Result<serde_json::Value> { - let handle = tokio::runtime::Handle::current(); + async fn api_get(&self, path: &str) -> Result<serde_json::Value> { let url = self.api_url(path); let mut req = self .http_client @@ -87,7 +86,7 @@ impl ForgejoDriver { if let Some(token) = &self.config.forgejo_token { req = req.header(AUTHORIZATION, format!("token {token}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { bail!( "Forgejo API request to {} failed: {}", @@ -95,16 +94,16 @@ impl ForgejoDriver { response.status() ); } - Ok(handle.block_on(response.json())?) + Ok(response.json().await?) } - fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { + async fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { let mut items = Vec::new(); let mut page = 1; loop { let sep = if path.contains('?') { "&" } else { "?" }; let paged_path = format!("{path}{sep}limit=50&page={page}"); - let data = self.api_get(&paged_path)?; + let data = self.api_get(&paged_path).await?; let batch: Vec<serde_json::Value> = match data { serde_json::Value::Array(arr) => arr, _ => break, @@ -121,14 +120,14 @@ impl ForgejoDriver { Ok(items) } - fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { + async fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { if self.git_driver.is_none() { let git_url = format!( "{}://{}/{}/{}.git", self.scheme, self.host, self.owner, self.repo ); let mut driver = GitDriver::new(&git_url, self.config.clone()); - driver.initialize()?; + driver.initialize().await?; self.git_driver = Some(Box::new(driver)); } Ok(self.git_driver.as_mut().unwrap()) @@ -136,8 +135,8 @@ impl ForgejoDriver { } impl VcsDriver for ForgejoDriver { - fn initialize(&mut self) -> Result<()> { - match self.api_get("") { + async fn initialize(&mut self) -> Result<()> { + match self.api_get("").await { Ok(data) => { let default_branch = data["default_branch"] .as_str() @@ -147,7 +146,7 @@ impl VcsDriver for ForgejoDriver { } Err(_) => { self.api_failed = true; - let driver = self.use_git_fallback()?; + let driver = self.use_git_fallback().await?; self.root_identifier = Some(driver.root_identifier().to_string()); } } @@ -158,14 +157,14 @@ impl VcsDriver for ForgejoDriver { self.root_identifier.as_deref().unwrap_or("main") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let branches = driver.branches()?.clone(); + let driver = self.use_git_fallback().await?; + let branches = driver.branches().await?.clone(); self.branches = Some(branches); } else { - let items = self.api_get_paginated("/branches")?; + let items = self.api_get_paginated("/branches").await?; let mut branches = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -180,14 +179,14 @@ impl VcsDriver for ForgejoDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let tags = driver.tags()?.clone(); + let driver = self.use_git_fallback().await?; + let tags = driver.tags().await?.clone(); self.tags = Some(tags); } else { - let items = self.api_get_paginated("/tags")?; + let items = self.api_get_paginated("/tags").await?; let mut tags = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = ( @@ -203,23 +202,26 @@ impl VcsDriver for ForgejoDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = content.and_then(|c| serde_json::from_str(&c).ok()); self.info_cache .insert(identifier.to_string(), value.clone()); Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } let path = format!("/contents/{}?ref={}", file, identifier); - match self.api_get(&path) { + match self.api_get(&path).await { Ok(data) => { if let Some(content) = data["content"].as_str() { // Forgejo returns base64-encoded content @@ -233,17 +235,17 @@ impl VcsDriver for ForgejoDriver { } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } - match self.api_get(&format!("/git/commits/{identifier}")) { + match self.api_get(&format!("/git/commits/{identifier}")).await { Ok(data) => Ok(data["created"].as_str().map(|s| s.to_string())), Err(_) => Ok(None), } } - fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { Ok(Some(DistReference { dist_type: "zip".to_string(), url: format!( @@ -270,9 +272,9 @@ impl VcsDriver for ForgejoDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { if let Some(driver) = &mut self.git_driver { - driver.cleanup()?; + driver.cleanup().await?; } Ok(()) } diff --git a/crates/mozart-vcs/src/driver/git.rs b/crates/mozart-vcs/src/driver/git.rs index 13fc243..cc9a210 100644 --- a/crates/mozart-vcs/src/driver/git.rs +++ b/crates/mozart-vcs/src/driver/git.rs @@ -131,7 +131,7 @@ impl GitDriver { } impl VcsDriver for GitDriver { - fn initialize(&mut self) -> Result<()> { + async fn initialize(&mut self) -> Result<()> { if self.is_local { // Local repo: use directly (or its .git subdir) let path = Path::new(&self.url); @@ -174,7 +174,7 @@ impl VcsDriver for GitDriver { self.root_identifier.as_deref().unwrap_or("master") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { let repo_dir = self.get_repo_dir()?.to_path_buf(); let process = ProcessExecutor::new(); @@ -187,7 +187,7 @@ impl VcsDriver for GitDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { let repo_dir = self.get_repo_dir()?.to_path_buf(); let process = ProcessExecutor::new(); @@ -204,12 +204,15 @@ impl VcsDriver for GitDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = match content { Some(c) => serde_json::from_str(&c).ok(), None => None, @@ -220,7 +223,7 @@ impl VcsDriver for GitDriver { Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { let repo_dir = self.get_repo_dir()?; let process = ProcessExecutor::new(); let resource = format!("{identifier}:{file}"); @@ -232,7 +235,7 @@ impl VcsDriver for GitDriver { } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { let repo_dir = self.get_repo_dir()?; let process = ProcessExecutor::new(); let output = process.execute( @@ -251,7 +254,7 @@ impl VcsDriver for GitDriver { } } - fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { // Plain git repos don't provide dist archives Ok(None) } @@ -268,7 +271,7 @@ impl VcsDriver for GitDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { Ok(()) } } diff --git a/crates/mozart-vcs/src/driver/github.rs b/crates/mozart-vcs/src/driver/github.rs index 23eaa8a..724cb35 100644 --- a/crates/mozart-vcs/src/driver/github.rs +++ b/crates/mozart-vcs/src/driver/github.rs @@ -65,8 +65,7 @@ impl GitHubDriver { ) } - fn api_get(&self, path: &str) -> Result<serde_json::Value> { - let handle = tokio::runtime::Handle::current(); + async fn api_get(&self, path: &str) -> Result<serde_json::Value> { let url = self.api_url(path); let mut req = self .http_client @@ -78,7 +77,7 @@ impl GitHubDriver { req = req.header(AUTHORIZATION, format!("token {token}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { bail!( "GitHub API request to {} failed with status {}", @@ -86,11 +85,10 @@ impl GitHubDriver { response.status() ); } - Ok(handle.block_on(response.json())?) + Ok(response.json().await?) } - fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { - let handle = tokio::runtime::Handle::current(); + async fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { let mut items = Vec::new(); let mut page = 1; loop { @@ -108,12 +106,12 @@ impl GitHubDriver { req = req.header(AUTHORIZATION, format!("token {token}")); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { bail!("GitHub API paginated request failed: {}", response.status()); } - let batch: Vec<serde_json::Value> = handle.block_on(response.json())?; + let batch: Vec<serde_json::Value> = response.json().await?; if batch.is_empty() { break; } @@ -127,11 +125,11 @@ impl GitHubDriver { Ok(items) } - fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { + async fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { if self.git_driver.is_none() { let git_url = format!("https://github.com/{}/{}.git", self.owner, self.repo); let mut driver = GitDriver::new(&git_url, self.config.clone()); - driver.initialize()?; + driver.initialize().await?; self.git_driver = Some(Box::new(driver)); } Ok(self.git_driver.as_mut().unwrap()) @@ -139,9 +137,9 @@ impl GitHubDriver { } impl VcsDriver for GitHubDriver { - fn initialize(&mut self) -> Result<()> { + async fn initialize(&mut self) -> Result<()> { // Try to fetch repo data from API - match self.api_get("") { + match self.api_get("").await { Ok(data) => { let default_branch = data["default_branch"] .as_str() @@ -152,7 +150,7 @@ impl VcsDriver for GitHubDriver { } Err(_) => { self.api_failed = true; - let driver = self.use_git_fallback()?; + let driver = self.use_git_fallback().await?; self.root_identifier = Some(driver.root_identifier().to_string()); } } @@ -163,14 +161,14 @@ impl VcsDriver for GitHubDriver { self.root_identifier.as_deref().unwrap_or("main") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let branches = driver.branches()?.clone(); + let driver = self.use_git_fallback().await?; + let branches = driver.branches().await?.clone(); self.branches = Some(branches); } else { - let items = self.api_get_paginated("/branches")?; + let items = self.api_get_paginated("/branches").await?; let mut branches = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -185,14 +183,14 @@ impl VcsDriver for GitHubDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let tags = driver.tags()?.clone(); + let driver = self.use_git_fallback().await?; + let tags = driver.tags().await?.clone(); self.tags = Some(tags); } else { - let items = self.api_get_paginated("/tags")?; + let items = self.api_get_paginated("/tags").await?; let mut tags = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -207,12 +205,15 @@ impl VcsDriver for GitHubDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = match content { Some(c) => serde_json::from_str(&c).ok(), None => None, @@ -223,7 +224,7 @@ impl VcsDriver for GitHubDriver { Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { if self.api_failed { // Can't use API, would need git fallback // For simplicity, return None (git_driver is mutable) @@ -231,7 +232,7 @@ impl VcsDriver for GitHubDriver { } let path = format!("/contents/{}?ref={}", file, identifier); - match self.api_get(&path) { + match self.api_get(&path).await { Ok(data) => { if let Some(content) = data["content"].as_str() { // GitHub returns base64-encoded content @@ -245,13 +246,13 @@ impl VcsDriver for GitHubDriver { } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } let path = format!("/commits/{}", identifier); - match self.api_get(&path) { + match self.api_get(&path).await { Ok(data) => { let date = data["commit"]["committer"]["date"] .as_str() @@ -262,7 +263,7 @@ impl VcsDriver for GitHubDriver { } } - fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { Ok(Some(DistReference { dist_type: "zip".to_string(), url: format!( @@ -286,9 +287,9 @@ impl VcsDriver for GitHubDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { if let Some(driver) = &mut self.git_driver { - driver.cleanup()?; + driver.cleanup().await?; } Ok(()) } diff --git a/crates/mozart-vcs/src/driver/gitlab.rs b/crates/mozart-vcs/src/driver/gitlab.rs index ed88f27..7b1a93b 100644 --- a/crates/mozart-vcs/src/driver/gitlab.rs +++ b/crates/mozart-vcs/src/driver/gitlab.rs @@ -80,8 +80,7 @@ impl GitLabDriver { ) } - fn api_get(&self, path: &str) -> Result<serde_json::Value> { - let handle = tokio::runtime::Handle::current(); + async fn api_get(&self, path: &str) -> Result<serde_json::Value> { let url = self.api_url(path); let mut req = self .http_client @@ -93,7 +92,7 @@ impl GitLabDriver { req = req.header("PRIVATE-TOKEN", token.as_str()); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if !response.status().is_success() { bail!( "GitLab API request to {} failed with status {}", @@ -101,16 +100,16 @@ impl GitLabDriver { response.status() ); } - Ok(handle.block_on(response.json())?) + Ok(response.json().await?) } - fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { + async fn api_get_paginated(&self, path: &str) -> Result<Vec<serde_json::Value>> { let mut items = Vec::new(); let mut page = 1; loop { let sep = if path.contains('?') { "&" } else { "?" }; let paged_path = format!("{path}{sep}per_page=100&page={page}"); - let data = self.api_get(&paged_path)?; + let data = self.api_get(&paged_path).await?; let batch: Vec<serde_json::Value> = match data { serde_json::Value::Array(arr) => arr, _ => break, @@ -127,14 +126,14 @@ impl GitLabDriver { Ok(items) } - fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { + async fn use_git_fallback(&mut self) -> Result<&mut GitDriver> { if self.git_driver.is_none() { let git_url = format!( "{}://{}/{}/{}.git", self.scheme, self.host, self.owner, self.repo ); let mut driver = GitDriver::new(&git_url, self.config.clone()); - driver.initialize()?; + driver.initialize().await?; self.git_driver = Some(Box::new(driver)); } Ok(self.git_driver.as_mut().unwrap()) @@ -142,8 +141,8 @@ impl GitLabDriver { } impl VcsDriver for GitLabDriver { - fn initialize(&mut self) -> Result<()> { - match self.api_get("") { + async fn initialize(&mut self) -> Result<()> { + match self.api_get("").await { Ok(data) => { if let Some(id) = data["id"].as_u64() { self.project_id = Some(id.to_string()); @@ -156,7 +155,7 @@ impl VcsDriver for GitLabDriver { } Err(_) => { self.api_failed = true; - let driver = self.use_git_fallback()?; + let driver = self.use_git_fallback().await?; self.root_identifier = Some(driver.root_identifier().to_string()); } } @@ -167,14 +166,14 @@ impl VcsDriver for GitLabDriver { self.root_identifier.as_deref().unwrap_or("main") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let branches = driver.branches()?.clone(); + let driver = self.use_git_fallback().await?; + let branches = driver.branches().await?.clone(); self.branches = Some(branches); } else { - let items = self.api_get_paginated("/repository/branches")?; + let items = self.api_get_paginated("/repository/branches").await?; let mut branches = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -189,14 +188,14 @@ impl VcsDriver for GitLabDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { if self.api_failed { - let driver = self.use_git_fallback()?; - let tags = driver.tags()?.clone(); + let driver = self.use_git_fallback().await?; + let tags = driver.tags().await?.clone(); self.tags = Some(tags); } else { - let items = self.api_get_paginated("/repository/tags")?; + let items = self.api_get_paginated("/repository/tags").await?; let mut tags = BTreeMap::new(); for item in items { if let (Some(name), Some(sha)) = @@ -211,22 +210,24 @@ impl VcsDriver for GitLabDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = content.and_then(|c| serde_json::from_str(&c).ok()); self.info_cache .insert(identifier.to_string(), value.clone()); Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } - let handle = tokio::runtime::Handle::current(); let encoded_file = file.replace('/', "%2F"); let path = format!("/repository/files/{}/raw?ref={}", encoded_file, identifier); let url = self.api_url(&path); @@ -234,25 +235,28 @@ impl VcsDriver for GitLabDriver { if let Some(token) = &self.config.gitlab_token { req = req.header("PRIVATE-TOKEN", token.as_str()); } - let response = handle.block_on(req.send())?; + let response = req.send().await?; if response.status().is_success() { - Ok(Some(handle.block_on(response.text())?)) + Ok(Some(response.text().await?)) } else { Ok(None) } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { if self.api_failed { return Ok(None); } - match self.api_get(&format!("/repository/commits/{identifier}")) { + match self + .api_get(&format!("/repository/commits/{identifier}")) + .await + { Ok(data) => Ok(data["committed_date"].as_str().map(|s| s.to_string())), Err(_) => Ok(None), } } - fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { Ok(Some(DistReference { dist_type: "zip".to_string(), url: format!( @@ -284,9 +288,9 @@ impl VcsDriver for GitLabDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { if let Some(driver) = &mut self.git_driver { - driver.cleanup()?; + driver.cleanup().await?; } Ok(()) } diff --git a/crates/mozart-vcs/src/driver/hg.rs b/crates/mozart-vcs/src/driver/hg.rs index 7bfb07e..f884c50 100644 --- a/crates/mozart-vcs/src/driver/hg.rs +++ b/crates/mozart-vcs/src/driver/hg.rs @@ -49,7 +49,7 @@ impl HgDriver { } impl VcsDriver for HgDriver { - fn initialize(&mut self) -> Result<()> { + async fn initialize(&mut self) -> Result<()> { let cache_dir = self.config.cache_dir.join("hg"); std::fs::create_dir_all(&cache_dir)?; let repo_dir = cache_dir.join(crate::util::git::GitUtil::sanitize_url(&self.url)); @@ -83,7 +83,7 @@ impl VcsDriver for HgDriver { self.root_identifier.as_deref().unwrap_or("default") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { let repo_dir = self.get_repo_dir()?.clone(); let mut branches = BTreeMap::new(); @@ -121,7 +121,7 @@ impl VcsDriver for HgDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { let repo_dir = self.get_repo_dir()?.clone(); let output = self.hg_util.execute(&["tags", "-q"], Some(&repo_dir))?; @@ -142,18 +142,21 @@ impl VcsDriver for HgDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = content.and_then(|c| serde_json::from_str(&c).ok()); self.info_cache .insert(identifier.to_string(), value.clone()); Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { let repo_dir = self.get_repo_dir()?; let output = self .hg_util @@ -165,7 +168,7 @@ impl VcsDriver for HgDriver { } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { let repo_dir = self.get_repo_dir()?; let output = self.hg_util.execute( &["log", "-r", identifier, "--template", "{date|isodatesec}"], @@ -179,7 +182,7 @@ impl VcsDriver for HgDriver { } } - fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { Ok(None) } @@ -195,7 +198,7 @@ impl VcsDriver for HgDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { Ok(()) } } diff --git a/crates/mozart-vcs/src/driver/mod.rs b/crates/mozart-vcs/src/driver/mod.rs index f8e26ef..7a132ed 100644 --- a/crates/mozart-vcs/src/driver/mod.rs +++ b/crates/mozart-vcs/src/driver/mod.rs @@ -79,30 +79,31 @@ pub enum DriverType { /// The VCS driver interface. /// /// Corresponds to Composer's `VcsDriverInterface`. -pub trait VcsDriver { +trait VcsDriver { /// Initialize the driver (e.g., clone mirror, fetch API metadata). - fn initialize(&mut self) -> Result<()>; + async fn initialize(&mut self) -> Result<()>; /// The root identifier (default branch/trunk). fn root_identifier(&self) -> &str; /// All branches as `name -> commit_hash`. - fn branches(&mut self) -> Result<&BTreeMap<String, String>>; + async fn branches(&mut self) -> Result<&BTreeMap<String, String>>; /// All tags as `name -> commit_hash`. - fn tags(&mut self) -> Result<&BTreeMap<String, String>>; + async fn tags(&mut self) -> Result<&BTreeMap<String, String>>; /// Get composer.json content parsed as JSON for a given identifier. - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>>; + async fn composer_information(&mut self, identifier: &str) + -> Result<Option<serde_json::Value>>; /// Get raw file content at a given path and identifier. - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>>; + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>>; /// Get the change date for a given identifier (ISO 8601). - fn change_date(&self, identifier: &str) -> Result<Option<String>>; + async fn change_date(&self, identifier: &str) -> Result<Option<String>>; /// Get the dist reference for a given identifier. - fn dist(&self, identifier: &str) -> Result<Option<DistReference>>; + async fn dist(&self, identifier: &str) -> Result<Option<DistReference>>; /// Get the source reference for a given identifier. fn source(&self, identifier: &str) -> SourceReference; @@ -111,7 +112,97 @@ pub trait VcsDriver { fn url(&self) -> &str; /// Clean up resources (temp dirs, etc.). - fn cleanup(&mut self) -> Result<()>; + async fn cleanup(&mut self) -> Result<()>; +} + +/// Enum-dispatched VCS driver. +/// +/// Wraps all concrete driver types to allow static dispatch with async trait methods. +pub enum AnyVcsDriver { + GitHub(github::GitHubDriver), + GitLab(gitlab::GitLabDriver), + Bitbucket(bitbucket::BitbucketDriver), + Forgejo(forgejo::ForgejoDriver), + Git(git::GitDriver), + Svn(svn::SvnDriver), + Hg(hg::HgDriver), +} + +macro_rules! dispatch { + ($self:expr, $method:ident $(, $arg:expr)*) => { + match $self { + AnyVcsDriver::GitHub(d) => d.$method($($arg),*), + AnyVcsDriver::GitLab(d) => d.$method($($arg),*), + AnyVcsDriver::Bitbucket(d) => d.$method($($arg),*), + AnyVcsDriver::Forgejo(d) => d.$method($($arg),*), + AnyVcsDriver::Git(d) => d.$method($($arg),*), + AnyVcsDriver::Svn(d) => d.$method($($arg),*), + AnyVcsDriver::Hg(d) => d.$method($($arg),*), + } + }; +} + +macro_rules! dispatch_async { + ($self:expr, $method:ident $(, $arg:expr)*) => { + match $self { + AnyVcsDriver::GitHub(d) => d.$method($($arg),*).await, + AnyVcsDriver::GitLab(d) => d.$method($($arg),*).await, + AnyVcsDriver::Bitbucket(d) => d.$method($($arg),*).await, + AnyVcsDriver::Forgejo(d) => d.$method($($arg),*).await, + AnyVcsDriver::Git(d) => d.$method($($arg),*).await, + AnyVcsDriver::Svn(d) => d.$method($($arg),*).await, + AnyVcsDriver::Hg(d) => d.$method($($arg),*).await, + } + }; +} + +impl AnyVcsDriver { + pub async fn initialize(&mut self) -> Result<()> { + dispatch_async!(self, initialize) + } + + pub fn root_identifier(&self) -> &str { + dispatch!(self, root_identifier) + } + + pub async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + dispatch_async!(self, branches) + } + + pub async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + dispatch_async!(self, tags) + } + + pub async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { + dispatch_async!(self, composer_information, identifier) + } + + pub async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + dispatch_async!(self, file_content, file, identifier) + } + + pub async fn change_date(&self, identifier: &str) -> Result<Option<String>> { + dispatch_async!(self, change_date, identifier) + } + + pub async fn dist(&self, identifier: &str) -> Result<Option<DistReference>> { + dispatch_async!(self, dist, identifier) + } + + pub fn source(&self, identifier: &str) -> SourceReference { + dispatch!(self, source, identifier) + } + + pub fn url(&self) -> &str { + dispatch!(self, url) + } + + pub async fn cleanup(&mut self) -> Result<()> { + dispatch_async!(self, cleanup) + } } /// Detect which driver type should handle a given URL. @@ -182,18 +273,16 @@ pub fn detect_driver( } /// Create a driver instance for the given URL and type. -pub fn create_driver( - url: &str, - driver_type: DriverType, - config: DriverConfig, -) -> Box<dyn VcsDriver> { +pub fn create_driver(url: &str, driver_type: DriverType, config: DriverConfig) -> AnyVcsDriver { match driver_type { - DriverType::GitHub => Box::new(github::GitHubDriver::new(url, config)), - DriverType::GitLab => Box::new(gitlab::GitLabDriver::new(url, config)), - DriverType::Bitbucket => Box::new(bitbucket::BitbucketDriver::new(url, config)), - DriverType::Forgejo => Box::new(forgejo::ForgejoDriver::new(url, config)), - DriverType::Git => Box::new(git::GitDriver::new(url, config)), - DriverType::Svn => Box::new(svn::SvnDriver::new(url, config)), - DriverType::Hg => Box::new(hg::HgDriver::new(url, config)), + DriverType::GitHub => AnyVcsDriver::GitHub(github::GitHubDriver::new(url, config)), + DriverType::GitLab => AnyVcsDriver::GitLab(gitlab::GitLabDriver::new(url, config)), + DriverType::Bitbucket => { + AnyVcsDriver::Bitbucket(bitbucket::BitbucketDriver::new(url, config)) + } + DriverType::Forgejo => AnyVcsDriver::Forgejo(forgejo::ForgejoDriver::new(url, config)), + DriverType::Git => AnyVcsDriver::Git(git::GitDriver::new(url, config)), + DriverType::Svn => AnyVcsDriver::Svn(svn::SvnDriver::new(url, config)), + DriverType::Hg => AnyVcsDriver::Hg(hg::HgDriver::new(url, config)), } } diff --git a/crates/mozart-vcs/src/driver/svn.rs b/crates/mozart-vcs/src/driver/svn.rs index 8b47f75..eea2d08 100644 --- a/crates/mozart-vcs/src/driver/svn.rs +++ b/crates/mozart-vcs/src/driver/svn.rs @@ -74,7 +74,7 @@ impl SvnDriver { } impl VcsDriver for SvnDriver { - fn initialize(&mut self) -> Result<()> { + async fn initialize(&mut self) -> Result<()> { let info = self.svn_info(&self.url)?; if let Some(url) = info["url"].as_str() { self.base_url = url.to_string(); @@ -87,7 +87,7 @@ impl VcsDriver for SvnDriver { self.root_identifier.as_deref().unwrap_or("HEAD") } - fn branches(&mut self) -> Result<&BTreeMap<String, String>> { + async fn branches(&mut self) -> Result<&BTreeMap<String, String>> { if self.branches.is_none() { let mut branches = BTreeMap::new(); @@ -117,7 +117,7 @@ impl VcsDriver for SvnDriver { Ok(self.branches.as_ref().unwrap()) } - fn tags(&mut self) -> Result<&BTreeMap<String, String>> { + async fn tags(&mut self) -> Result<&BTreeMap<String, String>> { if self.tags.is_none() { let mut tags = BTreeMap::new(); let tags_url = format!("{}/{}", self.base_url, self.tags_path); @@ -136,18 +136,21 @@ impl VcsDriver for SvnDriver { Ok(self.tags.as_ref().unwrap()) } - fn composer_information(&mut self, identifier: &str) -> Result<Option<serde_json::Value>> { + async fn composer_information( + &mut self, + identifier: &str, + ) -> Result<Option<serde_json::Value>> { if let Some(cached) = self.info_cache.get(identifier) { return Ok(cached.clone()); } - let content = self.file_content("composer.json", identifier)?; + let content = self.file_content("composer.json", identifier).await?; let value = content.and_then(|c| serde_json::from_str(&c).ok()); self.info_cache .insert(identifier.to_string(), value.clone()); Ok(value) } - fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { + async fn file_content(&self, file: &str, identifier: &str) -> Result<Option<String>> { // identifier is either a path (trunk, branches/x, tags/y) or a revision number let url = if identifier.contains('/') || identifier == "trunk" { format!("{}/{}/{}", self.base_url, identifier, file) @@ -164,7 +167,7 @@ impl VcsDriver for SvnDriver { } } - fn change_date(&self, identifier: &str) -> Result<Option<String>> { + async fn change_date(&self, identifier: &str) -> Result<Option<String>> { let url = if identifier.contains('/') || identifier == "trunk" { format!("{}/{}", self.base_url, identifier) } else { @@ -176,7 +179,7 @@ impl VcsDriver for SvnDriver { } } - fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { + async fn dist(&self, _identifier: &str) -> Result<Option<DistReference>> { // SVN doesn't provide dist archives Ok(None) } @@ -193,7 +196,7 @@ impl VcsDriver for SvnDriver { &self.url } - fn cleanup(&mut self) -> Result<()> { + async fn cleanup(&mut self) -> Result<()> { Ok(()) } } diff --git a/crates/mozart-vcs/src/repository.rs b/crates/mozart-vcs/src/repository.rs index 14f2ceb..b941eec 100644 --- a/crates/mozart-vcs/src/repository.rs +++ b/crates/mozart-vcs/src/repository.rs @@ -50,17 +50,17 @@ impl VcsRepository { /// 2. Reads composer.json from the root to get the package name /// 3. Scans tags → version releases /// 4. Scans branches → dev versions - pub fn scan(&self) -> Result<Vec<VcsPackageVersion>> { + pub async fn scan(&self) -> Result<Vec<VcsPackageVersion>> { let driver_type = self .driver_type .ok_or_else(|| anyhow::anyhow!("No suitable VCS driver found for URL: {}", self.url))?; let mut driver = create_driver(&self.url, driver_type, self.config.clone()); - driver.initialize()?; + driver.initialize().await?; // Get package name from root composer.json let root_id = driver.root_identifier().to_string(); - let root_info = driver.composer_information(&root_id)?; + let root_info = driver.composer_information(&root_id).await?; let package_name = match &root_info { Some(info) => info["name"] .as_str() @@ -81,14 +81,14 @@ impl VcsRepository { let mut versions = Vec::new(); // Scan tags - let tags = driver.tags()?.clone(); + let tags = driver.tags().await?.clone(); for (tag_name, tag_hash) in &tags { if let Some(version) = self.tag_to_version(tag_name) { - match driver.composer_information(tag_hash) { + match driver.composer_information(tag_hash).await { Ok(Some(info)) => { - let time = driver.change_date(tag_hash).unwrap_or(None); + let time = driver.change_date(tag_hash).await.unwrap_or(None); let source = driver.source(tag_hash); - let dist = driver.dist(tag_hash).unwrap_or(None); + let dist = driver.dist(tag_hash).await.unwrap_or(None); // Ensure name matches root package if info["name"].as_str() != Some(&package_name) { @@ -114,18 +114,18 @@ impl VcsRepository { } // Scan branches - let branches = driver.branches()?.clone(); + let branches = driver.branches().await?.clone(); let default_branch = driver.root_identifier().to_string(); for (branch_name, branch_hash) in &branches { - match driver.composer_information(branch_hash) { + match driver.composer_information(branch_hash).await { Ok(Some(info)) => { if info["name"].as_str() != Some(&package_name) { continue; } - let time = driver.change_date(branch_hash).unwrap_or(None); + let time = driver.change_date(branch_hash).await.unwrap_or(None); let source = driver.source(branch_hash); - let dist = driver.dist(branch_hash).unwrap_or(None); + let dist = driver.dist(branch_hash).await.unwrap_or(None); let is_default = branch_name == &default_branch; let version = self.branch_to_version(branch_name); @@ -154,7 +154,7 @@ impl VcsRepository { } } - driver.cleanup()?; + driver.cleanup().await?; Ok(versions) } diff --git a/crates/mozart-vcs/tests/git_driver_test.rs b/crates/mozart-vcs/tests/git_driver_test.rs index 1fafc7c..a8f0ce7 100644 --- a/crates/mozart-vcs/tests/git_driver_test.rs +++ b/crates/mozart-vcs/tests/git_driver_test.rs @@ -5,8 +5,7 @@ use tempfile::TempDir; use mozart_vcs::downloader::VcsDownloader; use mozart_vcs::downloader::git::GitDownloader; -use mozart_vcs::driver::git::GitDriver; -use mozart_vcs::driver::{DriverConfig, VcsDriver}; +use mozart_vcs::driver::{DriverConfig, DriverType, create_driver}; use mozart_vcs::process::ProcessExecutor; use mozart_vcs::util::git::GitUtil; @@ -66,8 +65,8 @@ fn create_test_repo(dir: &Path) { run(&["git", "checkout", "main"]); } -#[test] -fn test_git_driver_local_repo() { +#[tokio::test] +async fn test_git_driver_local_repo() { if !has_git() { eprintln!("Skipping test: git not available"); return; @@ -82,13 +81,13 @@ fn test_git_driver_local_repo() { ..DriverConfig::default() }; - let mut driver = GitDriver::new(repo_dir.path().to_str().unwrap(), config); + let mut driver = create_driver(repo_dir.path().to_str().unwrap(), DriverType::Git, config); - driver.initialize().unwrap(); + driver.initialize().await.unwrap(); assert_eq!(driver.root_identifier(), "main"); // Check tags - let tags = driver.tags().unwrap().clone(); + let tags = driver.tags().await.unwrap().clone(); assert!( tags.contains_key("v1.0.0"), "Missing tag v1.0.0: {:?}", @@ -101,7 +100,7 @@ fn test_git_driver_local_repo() { ); // Check branches - let branches = driver.branches().unwrap().clone(); + let branches = driver.branches().await.unwrap().clone(); assert!( branches.contains_key("main"), "Missing branch main: {:?}", @@ -115,25 +114,28 @@ fn test_git_driver_local_repo() { // Read composer.json let tag_hash = &tags["v1.0.0"]; - let info = driver.composer_information(tag_hash).unwrap(); + let info = driver.composer_information(tag_hash).await.unwrap(); assert!(info.is_some()); let info = info.unwrap(); assert_eq!(info["name"].as_str(), Some("test/package")); // Read file content - let content = driver.file_content("composer.json", tag_hash).unwrap(); + let content = driver + .file_content("composer.json", tag_hash) + .await + .unwrap(); assert!(content.is_some()); assert!(content.unwrap().contains("test/package")); // Change date - let date = driver.change_date(tag_hash).unwrap(); + let date = driver.change_date(tag_hash).await.unwrap(); assert!(date.is_some()); // Source reference let source = driver.source(tag_hash); assert_eq!(source.source_type, "git"); - driver.cleanup().unwrap(); + driver.cleanup().await.unwrap(); } #[test] @@ -223,8 +225,8 @@ fn test_detect_driver() { ); } -#[test] -fn test_vcs_repository_scan() { +#[tokio::test] +async fn test_vcs_repository_scan() { if !has_git() { eprintln!("Skipping test: git not available"); return; @@ -245,7 +247,7 @@ fn test_vcs_repository_scan() { config, ); - let versions = repo.scan().unwrap(); + let versions = repo.scan().await.unwrap(); assert!(!versions.is_empty(), "No versions found"); // Should find tag versions |
