aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-vcs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-23 12:10:44 +0900
committernsfisis <nsfisis@gmail.com>2026-02-23 12:14:11 +0900
commitd8ecb21a7931ec6f1d7b447d0c15f53de32bfc45 (patch)
tree0da1e0c2ec988905b98939b524b72217e83019de /crates/mozart-vcs
parent0080efea9386d46f65d1862fcb90eb44999d9761 (diff)
downloadphp-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.rs64
-rw-r--r--crates/mozart-vcs/src/driver/forgejo.rs58
-rw-r--r--crates/mozart-vcs/src/driver/git.rs21
-rw-r--r--crates/mozart-vcs/src/driver/github.rs61
-rw-r--r--crates/mozart-vcs/src/driver/gitlab.rs64
-rw-r--r--crates/mozart-vcs/src/driver/hg.rs21
-rw-r--r--crates/mozart-vcs/src/driver/mod.rs131
-rw-r--r--crates/mozart-vcs/src/driver/svn.rs21
-rw-r--r--crates/mozart-vcs/src/repository.rs24
-rw-r--r--crates/mozart-vcs/tests/git_driver_test.rs32
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