diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-06 22:40:30 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-06 22:54:44 +0900 |
| commit | c028a9d5c737de5d2b259638a0a76b999364a262 (patch) | |
| tree | 36c7678a335d284e6d2139346660c77f1e124ddc /crates/shirabe/src/repository | |
| parent | 33550b03c1724fbc8c1c8630c3b03fca1f0c0dc7 (diff) | |
| download | php-shirabe-c028a9d5c737de5d2b259638a0a76b999364a262.tar.gz php-shirabe-c028a9d5c737de5d2b259638a0a76b999364a262.tar.zst php-shirabe-c028a9d5c737de5d2b259638a0a76b999364a262.zip | |
feat(vcs): implement VcsDriverInterface for all VCS drivers
Reconcile the VcsDriverInterface trait with the concrete drivers so every
driver implements it as in PHP, where each driver extends VcsDriver.
- Make cache-mutating getters take &mut self in the trait, matching the
inherent methods' lazy-cache semantics; update vcs_repository consumers.
- Change supports() to take Rc<RefCell<Config>> and return Result<bool>,
completing GitDriver::supports deep ls-remote and its config wiring.
- Add base get_composer_information to Git/Hg/Fossil/Perforce and an
impl VcsDriverInterface block to every driver, delegating to inherent
methods and absorbing type differences in the impl.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/repository')
| -rw-r--r-- | crates/shirabe/src/repository/vcs/forgejo_driver.rs | 92 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/fossil_driver.rs | 103 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs | 81 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/git_driver.rs | 107 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/github_driver.rs | 92 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/gitlab_driver.rs | 92 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/hg_driver.rs | 105 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/perforce_driver.rs | 100 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/svn_driver.rs | 87 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/vcs_driver.rs | 53 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/vcs_driver_interface.rs | 21 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs_repository.rs | 12 |
12 files changed, 835 insertions, 110 deletions
diff --git a/crates/shirabe/src/repository/vcs/forgejo_driver.rs b/crates/shirabe/src/repository/vcs/forgejo_driver.rs index 30a0932..c93ee70 100644 --- a/crates/shirabe/src/repository/vcs/forgejo_driver.rs +++ b/crates/shirabe/src/repository/vcs/forgejo_driver.rs @@ -463,8 +463,8 @@ impl ForgejoDriver { .and_then(|v| v.clone())) } - pub fn get_source(&mut self, identifier: &str) -> IndexMap<String, String> { - if let Some(ref mut git_driver) = self.git_driver { + pub fn get_source(&self, identifier: &str) -> IndexMap<String, String> { + if let Some(ref git_driver) = self.git_driver { return git_driver.get_source(identifier); } @@ -490,17 +490,17 @@ impl ForgejoDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - config: &Config, + config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, _deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { let forgejo_url = ForgejoUrl::try_from(Some(url)); if forgejo_url.is_none() { - return false; + return Ok(false); } let forgejo_url = forgejo_url.unwrap(); - let forgejo_domains = config.get("forgejo-domains"); + let forgejo_domains = config.borrow().get("forgejo-domains"); let in_domains = if let Some(list) = forgejo_domains.as_list() { list.iter().any(|d| { d.as_string().map_or(false, |s| { @@ -511,7 +511,7 @@ impl ForgejoDriver { false }; if !in_domains { - return false; + return Ok(false); } if !extension_loaded("openssl") { @@ -524,10 +524,10 @@ impl ForgejoDriver { io_interface::VERBOSE, ); - return false; + return Ok(false); } - true + Ok(true) } fn setup_git_driver(&mut self, url: &str) -> Result<()> { @@ -687,3 +687,77 @@ impl ForgejoDriver { } } } + +impl crate::repository::vcs::VcsDriverInterface for ForgejoDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + ForgejoDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + ForgejoDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + ForgejoDriver::get_file_content(self, file, identifier) + } + + fn get_change_date( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<chrono::DateTime<chrono::Utc>>> { + ForgejoDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + ForgejoDriver::get_root_identifier(self) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + ForgejoDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + ForgejoDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(ForgejoDriver::get_dist(self, identifier)) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(ForgejoDriver::get_source(self, identifier)) + } + + fn get_url(&self) -> String { + ForgejoDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + ForgejoDriver::supports(io, config, url, deep) + } +} diff --git a/crates/shirabe/src/repository/vcs/fossil_driver.rs b/crates/shirabe/src/repository/vcs/fossil_driver.rs index 1e34f21..5b7ccc1 100644 --- a/crates/shirabe/src/repository/vcs/fossil_driver.rs +++ b/crates/shirabe/src/repository/vcs/fossil_driver.rs @@ -8,6 +8,7 @@ use shirabe_php_shim::{PhpMixed, RuntimeException, dirname, is_dir, is_file, is_ use crate::cache::Cache; use crate::config::Config; +use crate::downloader::TransportException; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::repository::vcs::VcsDriverBase; @@ -292,28 +293,28 @@ impl FossilDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - config: &Config, + _config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { if Preg::is_match( r"#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i", url, ) .unwrap_or(false) { - return true; + return Ok(true); } if Preg::is_match(r"!/fossil/|\.fossil!", url).unwrap_or(false) { - return true; + return Ok(true); } // local filesystem if Filesystem::is_local_path(url) { let url = Filesystem::get_platform_path(url); if !is_dir(&url) { - return false; + return Ok(false); } let mut process = ProcessExecutor::new(Some(io)); @@ -324,10 +325,98 @@ impl FossilDriver { Some(url), ) == 0 { - return true; + return Ok(true); } } - false + Ok(false) + } + + pub fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + if let Some(cached) = self.inner.read_cached_composer(identifier)? { + return Ok(cached); + } + + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })?; + + self.inner.write_cached_composer(identifier, composer) + } +} + +impl crate::repository::vcs::VcsDriverInterface for FossilDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + FossilDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + FossilDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + FossilDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + FossilDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + Ok(FossilDriver::get_root_identifier(self)) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + FossilDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + FossilDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(FossilDriver::get_dist(self, identifier)) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(FossilDriver::get_source(self, identifier)) + } + + fn get_url(&self) -> String { + FossilDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + FossilDriver::supports(io, config, url, deep) } } diff --git a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs index 644ac90..2a8d374 100644 --- a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs @@ -858,17 +858,17 @@ impl GitBitbucketDriver { /// @inheritDoc pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - _config: &Config, + _config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, _deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { if !Preg::is_match( r"#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i", url, ) .unwrap_or(false) { - return false; + return Ok(false); } if !extension_loaded("openssl") { @@ -881,9 +881,80 @@ impl GitBitbucketDriver { // PHP: writeError(..., true, io_interface::VERBOSE) // TODO(phase-b): io_interface::VERBOSE verbosity argument - return false; + return Ok(false); + } + + Ok(true) + } +} + +impl crate::repository::vcs::VcsDriverInterface for GitBitbucketDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + GitBitbucketDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + GitBitbucketDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + GitBitbucketDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + GitBitbucketDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + GitBitbucketDriver::get_root_identifier(self) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitBitbucketDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitBitbucketDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(GitBitbucketDriver::get_dist(self, identifier)) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(GitBitbucketDriver::get_source(self, identifier)) + } + + fn get_url(&self) -> String { + GitBitbucketDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } } + } - true + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + GitBitbucketDriver::supports(io, config, url, deep) } } diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs index 01aa759..4878ffe 100644 --- a/crates/shirabe/src/repository/vcs/git_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_driver.rs @@ -12,6 +12,7 @@ use shirabe_php_shim::{ use crate::cache::Cache; use crate::config::Config; +use crate::downloader::TransportException; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::repository::vcs::VcsDriverBase; @@ -19,6 +20,7 @@ use crate::util::Filesystem; use crate::util::Git as GitUtil; use crate::util::ProcessExecutor; use crate::util::Url; +use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct GitDriver { @@ -401,7 +403,7 @@ impl GitDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - _config: &Config, + config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, deep: bool, ) -> anyhow::Result<bool> { @@ -446,22 +448,15 @@ impl GitDriver { let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( io.clone(), )))); - // TODO(phase-b): supports() takes &Config; GitUtil now needs Rc<RefCell<Config>>. - // Skipping clean Rc construction since we cannot reconstruct one from a borrowed &Config. - let _ = _config; - return Err(anyhow::anyhow!( - "GitDriver::supports requires Rc<RefCell<Config>>: not yet ported" - )); - #[allow(unreachable_code)] let mut git_util = GitUtil::new( io.clone(), - todo!(), + config.clone(), process.clone(), std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), ); GitUtil::clean_env(&process); - let result = git_util.run_commands( + match git_util.run_commands( vec![vec![ "git".to_string(), "ls-remote".to_string(), @@ -473,63 +468,91 @@ impl GitDriver { Some(&sys_get_temp_dir()), false, None, - ); - match result { + ) { Ok(_) => Ok(true), - Err(_) => Ok(false), + Err(e) => { + if e.downcast_ref::<RuntimeException>().is_some() { + Ok(false) + } else { + Err(e) + } + } } } + + pub fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + if let Some(cached) = self.inner.read_cached_composer(identifier)? { + return Ok(cached); + } + + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })?; + + self.inner.write_cached_composer(identifier, composer) + } } -// TODO(phase-b): implement VcsDriverInterface for GitDriver — signatures here -// differ from the trait (some &mut self vs &self, different return shapes), so -// each method delegates via todo!() until reconciled. impl crate::repository::vcs::VcsDriverInterface for GitDriver { fn initialize(&mut self) -> anyhow::Result<()> { GitDriver::initialize(self) } fn get_composer_information( - &self, - _identifier: &str, - ) -> anyhow::Result<Option<IndexMap<String, shirabe_php_shim::PhpMixed>>> { - todo!() + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + GitDriver::get_composer_information(self, identifier) } - fn get_file_content(&self, _file: &str, _identifier: &str) -> anyhow::Result<Option<String>> { - todo!() + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + GitDriver::get_file_content(self, file, identifier) } - fn get_change_date(&self, _identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { - todo!() + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + GitDriver::get_change_date(self, identifier) } - fn get_root_identifier(&self) -> anyhow::Result<String> { - todo!() + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + GitDriver::get_root_identifier(self) } - fn get_branches(&self) -> anyhow::Result<IndexMap<String, String>> { - todo!() + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitDriver::get_branches(self) } - fn get_tags(&self) -> anyhow::Result<IndexMap<String, String>> { - todo!() + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitDriver::get_tags(self) } - fn get_dist(&self, _identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { - todo!() + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(GitDriver::get_dist(self, identifier)) } - fn get_source(&self, _identifier: &str) -> anyhow::Result<IndexMap<String, String>> { - todo!() + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(GitDriver::get_source(self, identifier)) } fn get_url(&self) -> String { GitDriver::get_url(self) } - fn has_composer_file(&self, _identifier: &str) -> anyhow::Result<bool> { - todo!() + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } } fn cleanup(&mut self) -> anyhow::Result<()> { @@ -537,11 +560,11 @@ impl crate::repository::vcs::VcsDriverInterface for GitDriver { } fn supports( - _io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - _config: &Config, - _url: &str, - _deep: bool, - ) -> bool { - todo!() + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + GitDriver::supports(io, config, url, deep) } } diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs index 093cd03..b3d5e05 100644 --- a/crates/shirabe/src/repository/vcs/github_driver.rs +++ b/crates/shirabe/src/repository/vcs/github_driver.rs @@ -939,10 +939,10 @@ impl GitHubDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - config: &Config, + config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, _deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if !Preg::is_match_strict_groups3( r"#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#", @@ -951,7 +951,7 @@ impl GitHubDriver { ) .unwrap_or(false) { - return false; + return Ok(false); } let origin_url = matches @@ -968,10 +968,10 @@ impl GitHubDriver { PhpMixed::String(strtolower( &Preg::replace(r"{^www\.}i", "", &origin_url).unwrap_or_default(), )), - &config.get("github-domains"), + &config.borrow().get("github-domains"), false, ) { - return false; + return Ok(false); } if !extension_loaded("openssl") { @@ -984,10 +984,10 @@ impl GitHubDriver { io_interface::VERBOSE, ); - return false; + return Ok(false); } - true + Ok(true) } /// Gives back the loaded <github-api>/repos/<owner>/<repo> result @@ -1324,3 +1324,81 @@ impl GitHubDriver { None } } + +impl crate::repository::vcs::VcsDriverInterface for GitHubDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + GitHubDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + GitHubDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + GitHubDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + GitHubDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + GitHubDriver::get_root_identifier(self) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitHubDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitHubDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(GitHubDriver::get_dist(self, identifier).map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect() + })) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(GitHubDriver::get_source(self, identifier) + .into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect()) + } + + fn get_url(&self) -> String { + GitHubDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + GitHubDriver::supports(io, config, url, deep) + } +} diff --git a/crates/shirabe/src/repository/vcs/gitlab_driver.rs b/crates/shirabe/src/repository/vcs/gitlab_driver.rs index 3e2cd38..84a569f 100644 --- a/crates/shirabe/src/repository/vcs/gitlab_driver.rs +++ b/crates/shirabe/src/repository/vcs/gitlab_driver.rs @@ -972,14 +972,14 @@ impl GitLabDriver { /// repository given. pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - config: &Config, + config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, _deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); if !Preg::is_match_strict_groups3(Self::URL_REGEX, url, Some(&mut match_)).unwrap_or(false) { - return false; + return Ok(false); } let scheme = match_ @@ -1005,14 +1005,14 @@ impl GitLabDriver { ); if Self::determine_origin( - &config.get("gitlab-domains"), + &config.borrow().get("gitlab-domains"), guessed_domain, &mut url_parts, match_.get(&CaptureKey::ByName("port".to_string())).cloned(), ) .is_none() { - return false; + return Ok(false); } if scheme == "https" && !extension_loaded("openssl") { @@ -1025,10 +1025,10 @@ impl GitLabDriver { io_interface::VERBOSE, ); - return false; + return Ok(false); } - true + Ok(true) } /// Gives back the loaded <gitlab-api>/projects/<owner>/<repo> result @@ -1122,3 +1122,81 @@ impl GitLabDriver { None } } + +impl crate::repository::vcs::VcsDriverInterface for GitLabDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + GitLabDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + GitLabDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + GitLabDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + GitLabDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + GitLabDriver::get_root_identifier(self) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitLabDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + GitLabDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(GitLabDriver::get_dist(self, identifier).map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect() + })) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(GitLabDriver::get_source(self, identifier) + .into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect()) + } + + fn get_url(&self) -> String { + GitLabDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + GitLabDriver::supports(io, config, url, deep) + } +} diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs index 9079d71..1686f5d 100644 --- a/crates/shirabe/src/repository/vcs/hg_driver.rs +++ b/crates/shirabe/src/repository/vcs/hg_driver.rs @@ -2,6 +2,7 @@ use crate::cache::Cache; use crate::config::Config; +use crate::downloader::TransportException; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::io::io_interface; @@ -12,7 +13,7 @@ use crate::util::Url; use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; -use shirabe_php_shim::{RuntimeException, dirname, is_dir, is_writable}; +use shirabe_php_shim::{PhpMixed, RuntimeException, dirname, is_dir, is_writable}; #[derive(Debug)] pub struct HgDriver { @@ -311,23 +312,23 @@ impl HgDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - config: &Config, + _config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { if Preg::is_match( r"#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i", url, ) .unwrap_or(false) { - return true; + return Ok(true); } if Filesystem::is_local_path(url) { let url = Filesystem::get_platform_path(url); if !is_dir(&url) { - return false; + return Ok(false); } let mut process = crate::util::ProcessExecutor::new(Some(io.clone())); @@ -338,12 +339,12 @@ impl HgDriver { Some(url), ) == 0 { - return true; + return Ok(true); } } if !deep { - return false; + return Ok(false); } let mut process = crate::util::ProcessExecutor::new(Some(io)); @@ -356,6 +357,94 @@ impl HgDriver { (), ); - exit == 0 + Ok(exit == 0) + } + + pub fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + if let Some(cached) = self.inner.read_cached_composer(identifier)? { + return Ok(cached); + } + + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })?; + + self.inner.write_cached_composer(identifier, composer) + } +} + +impl crate::repository::vcs::VcsDriverInterface for HgDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + HgDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + HgDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + HgDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + HgDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + HgDriver::get_root_identifier(self) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + HgDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + HgDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(HgDriver::get_dist(self, identifier)) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(HgDriver::get_source(self, identifier)) + } + + fn get_url(&self) -> String { + HgDriver::get_url(self) + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + HgDriver::supports(io, config, url, deep) } } diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs index 03948b3..d653869 100644 --- a/crates/shirabe/src/repository/vcs/perforce_driver.rs +++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs @@ -169,14 +169,34 @@ impl PerforceDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - _config: &Config, + _config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, deep: bool, - ) -> bool { + ) -> anyhow::Result<bool> { if deep || Preg::is_match(r"#\b(perforce|p4)\b#i", url).unwrap_or(false) { - return Perforce::check_server_exists(url, &mut ProcessExecutor::new(Some(io))); + return Ok(Perforce::check_server_exists( + url, + &mut ProcessExecutor::new(Some(io)), + )); } - false + Ok(false) + } + + pub fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + if let Some(cached) = self.inner.read_cached_composer(identifier)? { + return Ok(cached); + } + + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })?; + + self.inner.write_cached_composer(identifier, composer) } pub fn cleanup(&mut self) -> anyhow::Result<()> { @@ -193,3 +213,75 @@ impl PerforceDriver { &self.branch } } + +impl crate::repository::vcs::VcsDriverInterface for PerforceDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + PerforceDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + PerforceDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + PerforceDriver::get_file_content(self, file, identifier) + } + + fn get_change_date( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<chrono::DateTime<chrono::Utc>>> { + PerforceDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + Ok(PerforceDriver::get_root_identifier(self).to_string()) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + PerforceDriver::get_branches(self) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + PerforceDriver::get_tags(self) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(PerforceDriver::get_dist(self, identifier).map(|m| { + m.into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect() + })) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(PerforceDriver::get_source(self, identifier) + .into_iter() + .map(|(k, v)| (k, v.as_string().unwrap_or("").to_string())) + .collect()) + } + + fn get_url(&self) -> String { + PerforceDriver::get_url(self).to_string() + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + Ok(PerforceDriver::has_composer_file(self, identifier)) + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + PerforceDriver::cleanup(self) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + PerforceDriver::supports(io, config, url, deep) + } +} diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index fc5abb9..3d344ec 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -481,18 +481,18 @@ impl SvnDriver { pub fn supports( io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, - _config: &Config, + _config: std::rc::Rc<std::cell::RefCell<Config>>, url: &str, deep: bool, - ) -> bool { + ) -> Result<bool> { let url = Self::normalize_url(url); if Preg::is_match(r"#(^svn://|^svn\+ssh://|svn\.)#i", &url).unwrap_or(false) { - return true; + return Ok(true); } // proceed with deep check for local urls since they are fast to process if !deep && !Filesystem::is_local_path(&url) { - return false; + return Ok(false); } let mut process = ProcessExecutor::new(Some(io)); @@ -511,24 +511,24 @@ impl SvnDriver { if exit == 0 { // This is definitely a Subversion repository. - return true; + return Ok(true); } // Subversion client 1.7 and older if stripos(&process.get_error_output(), "authorization failed:").is_some() { // This is likely a remote Subversion repository that requires // authentication. We will handle actual authentication later. - return true; + return Ok(true); } // Subversion client 1.8 and newer if stripos(&process.get_error_output(), "Authentication failed").is_some() { // This is likely a remote Subversion or newer repository that requires // authentication. We will handle actual authentication later. - return true; + return Ok(true); } - false + Ok(false) } /// An absolute path (leading '/') is converted to a file:// url. @@ -607,3 +607,74 @@ impl SvnDriver { ) } } + +impl crate::repository::vcs::VcsDriverInterface for SvnDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + SvnDriver::initialize(self) + } + + fn get_composer_information( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + SvnDriver::get_composer_information(self, identifier) + } + + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>> { + SvnDriver::get_file_content(self, file, identifier) + } + + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + SvnDriver::get_change_date(self, identifier) + } + + fn get_root_identifier(&mut self) -> anyhow::Result<String> { + Ok(SvnDriver::get_root_identifier(self)) + } + + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>> { + Ok(self.get_branches().clone()) + } + + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>> { + Ok(self.get_tags().clone()) + } + + fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + Ok(SvnDriver::get_dist(self, identifier)) + } + + fn get_source(&self, identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + Ok(SvnDriver::get_source(self, identifier)) + } + + fn get_url(&self) -> String { + SvnDriver::get_url(self).to_string() + } + + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool> { + match self.get_composer_information(identifier) { + Ok(info) => Ok(info.is_some()), + Err(e) => { + if e.downcast_ref::<TransportException>().is_some() { + Ok(false) + } else { + Err(e) + } + } + } + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports( + io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> { + SvnDriver::supports(io, config, url, deep) + } +} diff --git a/crates/shirabe/src/repository/vcs/vcs_driver.rs b/crates/shirabe/src/repository/vcs/vcs_driver.rs index 8c3b571..d87924a 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver.rs @@ -130,6 +130,57 @@ impl VcsDriverBase { Ok(Some(composer)) } + + // Caching layer of the base `getComposerInformation`. Concrete drivers that + // inherit the base implementation thread their own `get_file_content` / + // `get_change_date` fetch between these two calls; splitting read and write + // keeps each `self.inner` borrow disjoint from the fetch's `self` borrow. + // Returns `Some(_)` when the value is already known (in-memory or on-disk + // cache), or `None` when the caller must fetch it. + pub fn read_cached_composer( + &mut self, + identifier: &str, + ) -> anyhow::Result<Option<Option<IndexMap<String, PhpMixed>>>> { + if self.info_cache.contains_key(identifier) { + return Ok(Some( + self.info_cache.get(identifier).and_then(|v| v.clone()), + )); + } + if self.should_cache(identifier) { + if let Some(res) = self.cache.as_mut().and_then(|c| c.read(identifier)) { + let parsed = JsonFile::parse_json(Some(&res), None)?; + let composer: Option<IndexMap<String, PhpMixed>> = parsed + .as_array() + .map(|m| m.iter().map(|(k, v)| (k.clone(), (**v).clone())).collect()); + self.info_cache + .insert(identifier.to_string(), composer.clone()); + return Ok(Some(composer)); + } + } + Ok(None) + } + + pub fn write_cached_composer( + &mut self, + identifier: &str, + composer: Option<IndexMap<String, PhpMixed>>, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + if self.should_cache(identifier) { + let encoded = JsonFile::encode_with_options( + &composer + .clone() + .map(PhpMixed::from) + .unwrap_or(PhpMixed::Null), + JsonEncodeOptions { + pretty_print: false, + ..Default::default() + }, + ); + self.cache.as_mut().map(|c| c.write(identifier, &encoded)); + } + self.info_cache.insert(identifier.to_string(), composer); + Ok(self.info_cache.get(identifier).and_then(|v| v.clone())) + } } // TODO(phase-b): the constructor is `final` in PHP; concrete implementations must replicate the @@ -245,7 +296,7 @@ pub trait VcsDriver: VcsDriverInterface { } fn has_composer_file(&mut self, identifier: &str) -> bool { - match self.get_composer_information(identifier) { + match VcsDriver::get_composer_information(self, identifier) { Ok(Some(_)) => true, _ => false, } diff --git a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs index c002dc5..e845300 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs @@ -12,19 +12,19 @@ pub trait VcsDriverInterface: std::fmt::Debug { fn initialize(&mut self) -> anyhow::Result<()>; fn get_composer_information( - &self, + &mut self, identifier: &str, ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>>; - fn get_file_content(&self, file: &str, identifier: &str) -> anyhow::Result<Option<String>>; + fn get_file_content(&mut self, file: &str, identifier: &str) -> anyhow::Result<Option<String>>; - fn get_change_date(&self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>>; + fn get_change_date(&mut self, identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>>; - fn get_root_identifier(&self) -> anyhow::Result<String>; + fn get_root_identifier(&mut self) -> anyhow::Result<String>; - fn get_branches(&self) -> anyhow::Result<IndexMap<String, String>>; + fn get_branches(&mut self) -> anyhow::Result<IndexMap<String, String>>; - fn get_tags(&self) -> anyhow::Result<IndexMap<String, String>>; + fn get_tags(&mut self) -> anyhow::Result<IndexMap<String, String>>; fn get_dist(&self, identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>>; @@ -32,11 +32,16 @@ pub trait VcsDriverInterface: std::fmt::Debug { fn get_url(&self) -> String; - fn has_composer_file(&self, identifier: &str) -> anyhow::Result<bool>; + fn has_composer_file(&mut self, identifier: &str) -> anyhow::Result<bool>; fn cleanup(&mut self) -> anyhow::Result<()>; - fn supports(io: Rc<RefCell<dyn IOInterface>>, config: &Config, url: &str, deep: bool) -> bool + fn supports( + io: Rc<RefCell<dyn IOInterface>>, + config: Rc<RefCell<Config>>, + url: &str, + deep: bool, + ) -> anyhow::Result<bool> where Self: Sized; } diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index 9a39b76..e90ac62 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -416,7 +416,7 @@ impl VcsRepository { } let result: Result<()> = (|| -> Result<()> { - let driver = self.driver.as_ref().unwrap(); + let driver = self.driver.as_mut().unwrap(); let data_opt = driver.get_composer_information(&identifier)?; if data_opt.is_none() { if is_very_verbose { @@ -667,8 +667,11 @@ impl VcsRepository { } let result: Result<()> = (|| -> Result<()> { - let driver = self.driver.as_ref().unwrap(); - let data_opt = driver.get_composer_information(&identifier)?; + let data_opt = self + .driver + .as_mut() + .unwrap() + .get_composer_information(&identifier)?; if data_opt.is_none() { if is_very_verbose { self.io.write_error(&format!( @@ -689,7 +692,7 @@ impl VcsRepository { ); data.shift_remove("default-branch"); - if driver.get_root_identifier()? == branch { + if self.driver.as_mut().unwrap().get_root_identifier()? == branch { data.insert("default-branch".to_string(), PhpMixed::Bool(true)); } @@ -703,6 +706,7 @@ impl VcsRepository { )); } + let driver = self.driver.as_ref().unwrap(); let package_data = self.pre_process(&**driver, data, &identifier)?; let package = self .loader |
